all: inline sum types (#12912)

pull/12963/head
Daniel Däschle 2021-12-25 16:26:40 +01:00 committed by GitHub
parent 485b392cb3
commit 35282396ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 312 additions and 114 deletions

View File

@ -1083,7 +1083,7 @@ pub:
comments []Comment comments []Comment
} }
// New implementation of sum types // SumTypeDecl is the ast node for `type MySumType = string | int`
pub struct SumTypeDecl { pub struct SumTypeDecl {
pub: pub:
name string name string

View File

@ -918,6 +918,7 @@ pub:
pub mut: pub mut:
fields []StructField fields []StructField
found_fields bool found_fields bool
is_anon bool
// generic sumtype support // generic sumtype support
is_generic bool is_generic bool
generic_types []Type generic_types []Type
@ -1048,6 +1049,10 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]
} }
else {} else {}
} }
} else if sym.info is SumType && (sym.info as SumType).is_anon {
variant_names := sym.info.variants.map(t.shorten_user_defined_typenames(t.sym(it).name,
import_aliases))
res = '${variant_names.join(' | ')}'
} else { } else {
res = t.shorten_user_defined_typenames(res, import_aliases) res = t.shorten_user_defined_typenames(res, import_aliases)
} }
@ -1089,7 +1094,7 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]
res = strings.repeat(`&`, nr_muls) + res res = strings.repeat(`&`, nr_muls) + res
} }
if typ.has_flag(.optional) { if typ.has_flag(.optional) {
res = '?' + res res = '?$res'
} }
return res return res
} }

View File

@ -0,0 +1,13 @@
import v.token
struct Foo {
bar string | int
}
interface Egg {
milk string | int
}
fn foo(bar string | int) int | string | token.Position {
return 1
}

View File

@ -434,7 +434,12 @@ fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_
g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto') g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto')
mut fn_builder := strings.new_builder(512) mut fn_builder := strings.new_builder(512)
fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) {') fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) {')
mut clean_sum_type_v_type_name := styp.replace('__', '.') mut clean_sum_type_v_type_name := ''
if info.is_anon {
variant_names := info.variants.map(g.table.sym(it).name)
clean_sum_type_v_type_name = '(${variant_names.join(' | ')})'
} else {
clean_sum_type_v_type_name = styp.replace('__', '.')
if styp.ends_with('*') { if styp.ends_with('*') {
clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '') clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '')
} }
@ -444,6 +449,7 @@ fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_
'>' '>'
} }
clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name) clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name)
}
fn_builder.writeln('\tswitch(x._typ) {') fn_builder.writeln('\tswitch(x._typ) {')
for typ in info.variants { for typ in info.variants {
typ_str := g.typ(typ) typ_str := g.typ(typ)

View File

@ -539,6 +539,10 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
} }
fn (mut p Parser) fn_receiver(mut params []ast.Param, mut rec ReceiverParsingInfo) ? { fn (mut p Parser) fn_receiver(mut params []ast.Param, mut rec ReceiverParsingInfo) ? {
p.inside_receiver_param = true
defer {
p.inside_receiver_param = false
}
lpar_pos := p.tok.position() lpar_pos := p.tok.position()
p.next() // ( p.next() // (
is_shared := p.tok.kind == .key_shared is_shared := p.tok.kind == .key_shared

View File

@ -8,6 +8,10 @@ import v.ast
import v.util import v.util
import v.token import v.token
const (
maximum_inline_sum_type_variants = 3
)
pub fn (mut p Parser) parse_array_type(expecting token.Kind) ast.Type { pub fn (mut p Parser) parse_array_type(expecting token.Kind) ast.Type {
p.check(expecting) p.check(expecting)
// fixed array // fixed array
@ -283,9 +287,74 @@ pub fn (mut p Parser) parse_language() ast.Language {
return language return language
} }
// parse_inline_sum_type parses the type and registers it in case the type is an anonymous sum type.
// It also takes care of inline sum types where parse_type only parses a standalone type.
pub fn (mut p Parser) parse_inline_sum_type() ast.Type {
variants := p.parse_sum_type_variants()
if variants.len > 1 {
if variants.len > parser.maximum_inline_sum_type_variants {
pos := variants[0].pos.extend(variants[variants.len - 1].pos)
p.warn_with_pos('an inline sum type expects a maximum of $parser.maximum_inline_sum_type_variants types ($variants.len were given)',
pos)
}
mut variant_names := variants.map(p.table.sym(it.typ).name)
variant_names.sort()
// deterministic name
name := '_v_anon_sum_type_${variant_names.join('_')}'
variant_types := variants.map(it.typ)
prepend_mod_name := p.prepend_mod(name)
mut idx := p.table.find_type_idx(prepend_mod_name)
if idx > 0 {
return ast.new_type(idx)
}
idx = p.table.register_type_symbol(ast.TypeSymbol{
kind: .sum_type
name: prepend_mod_name
cname: util.no_dots(prepend_mod_name)
mod: p.mod
info: ast.SumType{
is_anon: true
variants: variant_types
}
})
return ast.new_type(idx)
} else if variants.len == 1 {
return variants[0].typ
}
return ast.Type(0)
}
// parse_sum_type_variants parses several types separated with a pipe and returns them as a list with at least one node.
// If there is less than one node, it will add an error to the error list.
pub fn (mut p Parser) parse_sum_type_variants() []ast.TypeNode {
p.inside_sum_type = true
defer {
p.inside_sum_type = false
}
mut types := []ast.TypeNode{}
for {
type_start_pos := p.tok.position()
typ := p.parse_type()
// TODO: needs to be its own var, otherwise TCC fails because of a known stack error
prev_tok := p.prev_tok
type_end_pos := prev_tok.position()
type_pos := type_start_pos.extend(type_end_pos)
types << ast.TypeNode{
typ: typ
pos: type_pos
}
if p.tok.kind != .pipe {
break
}
p.check(.pipe)
}
return types
}
pub fn (mut p Parser) parse_type() ast.Type { pub fn (mut p Parser) parse_type() ast.Type {
// optional // optional
mut is_optional := false mut is_optional := false
optional_pos := p.tok.position()
if p.tok.kind == .question { if p.tok.kind == .question {
line_nr := p.tok.line_nr line_nr := p.tok.line_nr
p.next() p.next()
@ -333,6 +402,10 @@ pub fn (mut p Parser) parse_type() ast.Type {
p.error_with_pos('use `?` instead of `?void`', pos) p.error_with_pos('use `?` instead of `?void`', pos)
return 0 return 0
} }
sym := p.table.sym(typ)
if is_optional && sym.info is ast.SumType && (sym.info as ast.SumType).is_anon {
p.error_with_pos('an inline sum type cannot be optional', optional_pos.extend(p.prev_tok.position()))
}
} }
if is_optional { if is_optional {
typ = typ.set_flag(.optional) typ = typ.set_flag(.optional)
@ -363,7 +436,6 @@ pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_d
name = 'JS.$name' name = 'JS.$name'
} else if p.peek_tok.kind == .dot && check_dot { } else if p.peek_tok.kind == .dot && check_dot {
// `module.Type` // `module.Type`
// /if !(p.tok.lit in p.table.imports) {
mut mod := name mut mod := name
mut mod_pos := p.tok.position() mut mod_pos := p.tok.position()
p.next() p.next()
@ -394,7 +466,7 @@ pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_d
p.error('imported types must start with a capital letter') p.error('imported types must start with a capital letter')
return 0 return 0
} }
} else if p.expr_mod != '' && !p.in_generic_params { // p.expr_mod is from the struct and not from the generic parameter } else if p.expr_mod != '' && !p.inside_generic_params { // p.expr_mod is from the struct and not from the generic parameter
name = p.expr_mod + '.' + name name = p.expr_mod + '.' + name
} else if name in p.imported_symbols { } else if name in p.imported_symbols {
name = p.imported_symbols[name] name = p.imported_symbols[name]
@ -402,7 +474,6 @@ pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_d
// `Foo` in module `mod` means `mod.Foo` // `Foo` in module `mod` means `mod.Foo`
name = p.mod + '.' + name name = p.mod + '.' + name
} }
// p.warn('get type $name')
match p.tok.kind { match p.tok.kind {
.key_fn { .key_fn {
// func // func
@ -412,7 +483,8 @@ pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_d
// array // array
return p.parse_array_type(p.tok.kind) return p.parse_array_type(p.tok.kind)
} }
.lpar { else {
if p.tok.kind == .lpar && !p.inside_sum_type {
// multiple return // multiple return
if is_ptr { if is_ptr {
p.error('parse_type: unexpected `&` before multiple returns') p.error('parse_type: unexpected `&` before multiple returns')
@ -420,8 +492,10 @@ pub fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_d
} }
return p.parse_multi_return_type() return p.parse_multi_return_type()
} }
else { if ((p.peek_tok.kind == .dot && p.peek_token(3).kind == .pipe)
// no p.next() || p.peek_tok.kind == .pipe) && !p.inside_sum_type && !p.inside_receiver_param {
return p.parse_inline_sum_type()
}
if name == 'map' { if name == 'map' {
return p.parse_map_type() return p.parse_map_type()
} }
@ -541,7 +615,7 @@ pub fn (mut p Parser) parse_generic_inst_type(name string) ast.Type {
mut bs_cname := name mut bs_cname := name
start_pos := p.tok.position() start_pos := p.tok.position()
p.next() p.next()
p.in_generic_params = true p.inside_generic_params = true
bs_name += '<' bs_name += '<'
bs_cname += '_T_' bs_cname += '_T_'
mut concrete_types := []ast.Type{} mut concrete_types := []ast.Type{}
@ -564,7 +638,7 @@ pub fn (mut p Parser) parse_generic_inst_type(name string) ast.Type {
} }
concrete_types_pos := start_pos.extend(p.tok.position()) concrete_types_pos := start_pos.extend(p.tok.position())
p.check(.gt) p.check(.gt)
p.in_generic_params = false p.inside_generic_params = false
bs_name += '>' bs_name += '>'
// fmt operates on a per-file basis, so is_instance might be not set correctly. Thus it's ignored. // fmt operates on a per-file basis, so is_instance might be not set correctly. Thus it's ignored.
if (is_instance || p.pref.is_fmt) && concrete_types.len > 0 { if (is_instance || p.pref.is_fmt) && concrete_types.len > 0 {

View File

@ -44,6 +44,17 @@ mut:
inside_str_interp bool inside_str_interp bool
inside_array_lit bool inside_array_lit bool
inside_in_array bool inside_in_array bool
inside_match bool // to separate `match A { }` from `Struct{}`
inside_select bool // to allow `ch <- Struct{} {` inside `select`
inside_match_case bool // to separate `match_expr { }` from `Struct{}`
inside_match_body bool // to fix eval not used TODO
inside_unsafe bool
inside_sum_type bool // to prevent parsing inline sum type again
inside_asm_template bool
inside_asm bool
inside_defer bool
inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function
inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method
or_is_handled bool // ignore `or` in this expression or_is_handled bool // ignore `or` in this expression
builtin_mod bool // are we in the `builtin` module? builtin_mod bool // are we in the `builtin` module?
mod string // current module name mod string // current module name
@ -60,11 +71,6 @@ mut:
imported_symbols map[string]string imported_symbols map[string]string
is_amp bool // for generating the right code for `&Foo{}` is_amp bool // for generating the right code for `&Foo{}`
returns bool returns bool
inside_match bool // to separate `match A { }` from `Struct{}`
inside_select bool // to allow `ch <- Struct{} {` inside `select`
inside_match_case bool // to separate `match_expr { }` from `Struct{}`
inside_match_body bool // to fix eval not used TODO
inside_unsafe bool
is_stmt_ident bool // true while the beginning of a statement is an ident/selector is_stmt_ident bool // true while the beginning of a statement is an ident/selector
expecting_type bool // `is Type`, expecting type expecting_type bool // `is Type`, expecting type
errors []errors.Error errors []errors.Error
@ -73,13 +79,9 @@ mut:
vet_errors []vet.Error vet_errors []vet.Error
cur_fn_name string cur_fn_name string
label_names []string label_names []string
in_generic_params bool // indicates if parsing between `<` and `>` of a method/function
name_error bool // indicates if the token is not a name or the name is on another line name_error bool // indicates if the token is not a name or the name is on another line
n_asm int // controls assembly labels n_asm int // controls assembly labels
inside_asm_template bool
inside_asm bool
global_labels []string global_labels []string
inside_defer bool
comptime_if_cond bool comptime_if_cond bool
defer_vars []ast.Ident defer_vars []ast.Ident
should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop
@ -3419,32 +3421,16 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
comments: comments comments: comments
} }
} }
first_type := p.parse_type() // need to parse the first type before we can check if it's `type A = X | Y` sum_variants << p.parse_sum_type_variants()
type_alias_pos := p.tok.position()
if p.tok.kind == .pipe {
mut type_end_pos := p.prev_tok.position()
type_pos = type_pos.extend(type_end_pos)
p.next()
sum_variants << ast.TypeNode{
typ: first_type
pos: type_pos
}
// type SumType = A | B | c // type SumType = A | B | c
for { if sum_variants.len > 1 {
type_pos = p.tok.position() for variant in sum_variants {
variant_type := p.parse_type() variant_sym := p.table.sym(variant.typ)
// TODO: needs to be its own var, otherwise TCC fails because of a known stack error // TODO: implement this check for error too
prev_tok := p.prev_tok if variant_sym.kind == .none_ {
type_end_pos = prev_tok.position() p.error_with_pos('named sum type cannot have none as its variant', variant.pos)
type_pos = type_pos.extend(type_end_pos) return ast.AliasTypeDecl{}
sum_variants << ast.TypeNode{
typ: variant_type
pos: type_pos
} }
if p.tok.kind != .pipe {
break
}
p.check(.pipe)
} }
variant_types := sum_variants.map(it.typ) variant_types := sum_variants.map(it.typ)
prepend_mod_name := p.prepend_mod(name) prepend_mod_name := p.prepend_mod(name)
@ -3481,7 +3467,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
p.error_with_pos('generic type aliases are not yet implemented', decl_pos_with_generics) p.error_with_pos('generic type aliases are not yet implemented', decl_pos_with_generics)
return ast.AliasTypeDecl{} return ast.AliasTypeDecl{}
} }
parent_type := first_type // sum_variants will have only one element
parent_type := sum_variants[0].typ
parent_sym := p.table.sym(parent_type) parent_sym := p.table.sym(parent_type)
pidx := parent_type.idx() pidx := parent_type.idx()
p.check_for_impure_v(parent_sym.language, decl_pos) p.check_for_impure_v(parent_sym.language, decl_pos)
@ -3505,6 +3492,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
return ast.AliasTypeDecl{} return ast.AliasTypeDecl{}
} }
if idx == pidx { if idx == pidx {
type_alias_pos := sum_variants[0].pos
p.error_with_pos('a type alias can not refer to itself: $name', decl_pos.extend(type_alias_pos)) p.error_with_pos('a type alias can not refer to itself: $name', decl_pos.extend(type_alias_pos))
return ast.AliasTypeDecl{} return ast.AliasTypeDecl{}
} }

View File

@ -0,0 +1,3 @@
interface Foo {
bar string | int
}

View File

@ -0,0 +1,5 @@
vlib/v/parser/tests/anon_sum_type_multi_return_err.vv:1:24: error: invalid expression: unexpected token `|`
1 | fn abc() (string, int) | string {
| ^
2 | return ''
3 | }

View File

@ -0,0 +1,3 @@
fn abc() (string, int) | string {
return ''
}

View File

@ -0,0 +1,5 @@
vlib/v/parser/tests/anon_sum_type_receiver_err.vv:1:11: error: unexpected token `|`, expecting `)`
1 | fn (x int | string) baz() {
| ^
2 | println('baz')
3 | }

View File

@ -0,0 +1,3 @@
fn (x int | string) baz() {
println('baz')
}

View File

@ -0,0 +1,3 @@
struct Foo {
bar string | int
}

View File

@ -0,0 +1,5 @@
vlib/v/parser/tests/inline_sum_type_optional_err.vv:1:10: error: an inline sum type cannot be optional
1 | fn foo() ?string | int {
| ~~~~~~~~~~~~~
2 | return 0
3 | }

View File

@ -0,0 +1,3 @@
fn foo() ?string | int {
return 0
}

View File

@ -0,0 +1,21 @@
vlib/v/parser/tests/inline_sum_type_return_type_too_many_variants.vv:4:6: warning: an inline sum type expects a maximum of 3 types (5 were given)
2 |
3 | struct Foo {
4 | bar int | string | token.Position | bool | u32
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | }
6 |
vlib/v/parser/tests/inline_sum_type_return_type_too_many_variants.vv:7:12: warning: an inline sum type expects a maximum of 3 types (5 were given)
5 | }
6 |
7 | fn foo(arg int | string | token.Position | bool | u32) int | string | token.Position | bool | u32 {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 | return 1
9 | }
vlib/v/parser/tests/inline_sum_type_return_type_too_many_variants.vv:7:56: warning: an inline sum type expects a maximum of 3 types (5 were given)
5 | }
6 |
7 | fn foo(arg int | string | token.Position | bool | u32) int | string | token.Position | bool | u32 {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 | return 1
9 | }

View File

@ -0,0 +1,9 @@
import v.token
struct Foo {
bar int | string | token.Position | bool | u32
}
fn foo(arg int | string | token.Position | bool | u32) int | string | token.Position | bool | u32 {
return 1
}

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/named_sum_type_none_err.vv:1:34: error: named sum type cannot have none as its variant
1 | type Abc = string | int | bool | none
| ~~~~

View File

@ -0,0 +1 @@
type Abc = string | int | bool | none

View File

@ -0,0 +1,5 @@
vlib/v/parser/tests/option_sum_type_return_err.vv:1:21: error: an inline sum type cannot be optional
1 | fn option_sumtype() ?string | int {
| ~~~~~~~~~~~~~
2 | return 0
3 | }

View File

@ -0,0 +1,3 @@
fn option_sumtype() ?string | int {
return 0
}

View File

@ -0,0 +1,41 @@
fn returns_sumtype() int | string {
return 1
}
fn returns_sumtype_reverse() int | string {
return 1
}
fn test_stringification() {
x := returns_sumtype()
y := returns_sumtype_reverse()
assert '$x' == '$y'
}
struct Milk {
egg int | string
}
fn test_struct_with_inline_sumtype() {
m := Milk{
egg: 1
}
assert m.egg is int
}
interface IMilk {
egg int | string
}
fn receive_imilk(milk IMilk) {}
fn test_interface_with_inline_sumtype() {
m := Milk{
egg: 1
}
receive_imilk(m)
}
fn returns_sumtype_in_multireturn() (int | string, string) {
return 1, ''
}

View File

@ -1,5 +0,0 @@
type MapValue = int | none
fn test_sum_type_with_none_type() {
assert true
}