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,16 +434,22 @@ 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 styp.ends_with('*') { if info.is_anon {
clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '') 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('*') {
clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '')
}
if clean_sum_type_v_type_name.contains('_T_') {
clean_sum_type_v_type_name =
clean_sum_type_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') +
'>'
}
clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name)
} }
if clean_sum_type_v_type_name.contains('_T_') {
clean_sum_type_v_type_name =
clean_sum_type_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') +
'>'
}
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,16 +483,19 @@ 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 {
// multiple return
if is_ptr {
p.error('parse_type: unexpected `&` before multiple returns')
return 0
}
return p.parse_multi_return_type()
}
else { else {
// no p.next() if p.tok.kind == .lpar && !p.inside_sum_type {
// multiple return
if is_ptr {
p.error('parse_type: unexpected `&` before multiple returns')
return 0
}
return p.parse_multi_return_type()
}
if ((p.peek_tok.kind == .dot && p.peek_token(3).kind == .pipe)
|| 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

@ -26,64 +26,66 @@ mut:
scanner &scanner.Scanner scanner &scanner.Scanner
comments_mode scanner.CommentsMode = .skip_comments comments_mode scanner.CommentsMode = .skip_comments
// see comment in parse_file // see comment in parse_file
tok token.Token tok token.Token
prev_tok token.Token prev_tok token.Token
peek_tok token.Token peek_tok token.Token
table &ast.Table table &ast.Table
language ast.Language language ast.Language
fn_language ast.Language // .c for `fn C.abcd()` declarations fn_language ast.Language // .c for `fn C.abcd()` declarations
inside_vlib_file bool // true for all vlib/ files inside_vlib_file bool // true for all vlib/ files
inside_test_file bool // when inside _test.v or _test.vv file inside_test_file bool // when inside _test.v or _test.vv file
inside_if bool inside_if bool
inside_if_expr bool inside_if_expr bool
inside_ct_if_expr bool inside_ct_if_expr bool
inside_or_expr bool inside_or_expr bool
inside_for bool inside_for bool
inside_fn bool // true even with implicit main inside_fn bool // true even with implicit main
inside_unsafe_fn bool inside_unsafe_fn bool
inside_str_interp bool inside_str_interp bool
inside_array_lit bool inside_array_lit bool
inside_in_array bool inside_in_array bool
or_is_handled bool // ignore `or` in this expression inside_match bool // to separate `match A { }` from `Struct{}`
builtin_mod bool // are we in the `builtin` module? inside_select bool // to allow `ch <- Struct{} {` inside `select`
mod string // current module name inside_match_case bool // to separate `match_expr { }` from `Struct{}`
is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree inside_match_body bool // to fix eval not used TODO
has_globals bool // `[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__ inside_unsafe bool
is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__. inside_sum_type bool // to prevent parsing inline sum type again
attrs []ast.Attr // attributes before next decl stmt inside_asm_template bool
expr_mod string // for constructing full type names in parse_type() inside_asm bool
scope &ast.Scope inside_defer bool
imports map[string]string // alias => mod_name inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function
ast_imports []ast.Import // mod_names inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method
used_imports []string // alias or_is_handled bool // ignore `or` in this expression
auto_imports []string // imports, the user does not need to specify builtin_mod bool // are we in the `builtin` module?
imported_symbols map[string]string mod string // current module name
is_amp bool // for generating the right code for `&Foo{}` is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree
returns bool has_globals bool // `[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__
inside_match bool // to separate `match A { }` from `Struct{}` is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__.
inside_select bool // to allow `ch <- Struct{} {` inside `select` attrs []ast.Attr // attributes before next decl stmt
inside_match_case bool // to separate `match_expr { }` from `Struct{}` expr_mod string // for constructing full type names in parse_type()
inside_match_body bool // to fix eval not used TODO scope &ast.Scope
inside_unsafe bool imports map[string]string // alias => mod_name
is_stmt_ident bool // true while the beginning of a statement is an ident/selector ast_imports []ast.Import // mod_names
expecting_type bool // `is Type`, expecting type used_imports []string // alias
errors []errors.Error auto_imports []string // imports, the user does not need to specify
warnings []errors.Warning imported_symbols map[string]string
notices []errors.Notice is_amp bool // for generating the right code for `&Foo{}`
vet_errors []vet.Error returns bool
cur_fn_name string is_stmt_ident bool // true while the beginning of a statement is an ident/selector
label_names []string expecting_type bool // `is Type`, expecting type
in_generic_params bool // indicates if parsing between `<` and `>` of a method/function errors []errors.Error
name_error bool // indicates if the token is not a name or the name is on another line warnings []errors.Warning
n_asm int // controls assembly labels notices []errors.Notice
inside_asm_template bool vet_errors []vet.Error
inside_asm bool cur_fn_name string
global_labels []string label_names []string
inside_defer bool name_error bool // indicates if the token is not a name or the name is on another line
comptime_if_cond bool n_asm int // controls assembly labels
defer_vars []ast.Ident global_labels []string
should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop comptime_if_cond bool
codegen_text string defer_vars []ast.Ident
should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop
codegen_text string
} }
// for tests // for tests
@ -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() // type SumType = A | B | c
if p.tok.kind == .pipe { if sum_variants.len > 1 {
mut type_end_pos := p.prev_tok.position() for variant in sum_variants {
type_pos = type_pos.extend(type_end_pos) variant_sym := p.table.sym(variant.typ)
p.next() // TODO: implement this check for error too
sum_variants << ast.TypeNode{ if variant_sym.kind == .none_ {
typ: first_type p.error_with_pos('named sum type cannot have none as its variant', variant.pos)
pos: type_pos return ast.AliasTypeDecl{}
}
// type SumType = A | B | c
for {
type_pos = p.tok.position()
variant_type := 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_pos.extend(type_end_pos)
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
}