parser: imporve generics detection (#8992)

pull/8995/head
zakuro 2021-02-27 17:07:18 +09:00 committed by GitHub
parent 81cf6f7ea2
commit 1fd0aceb42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 12 deletions

View File

@ -1115,31 +1115,54 @@ pub fn (mut p Parser) parse_ident(language table.Language) ast.Ident {
} }
} }
fn (p &Parser) is_typename(t token.Token) bool {
return t.kind == .name && (t.lit.is_capital() || p.table.known_type(t.lit))
}
// heuristics to detect `func<T>()` from `var < expr`
// 1. `f<[]` is generic(e.g. `f<[]int>`) because `var < []` is invalid
// 2. `f<map[` is generic(e.g. `f<map[string]string>)
// 3. `f<foo>` is generic because `v1 < foo > v2` is invalid syntax
// 4. `f<Foo,` is generic when Foo is typename.
// otherwise it is not generic because it may be multi-value (e.g. `return f < foo, 0`).
// 5. `f<mod.Foo>` is same as case 3
// 6. `f<mod.Foo,` is same as case 4
// 7. otherwise, it's not generic
// see also test_generic_detection in vlib/v/tests/generics_test.v
fn (p &Parser) is_generic_call() bool { fn (p &Parser) is_generic_call() bool {
lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 { lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 {
p.tok.lit[0].is_capital() p.tok.lit[0].is_capital()
} else { } else {
false false
} }
if lit0_is_capital || p.peek_tok.kind != .lt {
return false
}
tok2 := p.peek_token(2) tok2 := p.peek_token(2)
tok3 := p.peek_token(3) tok3 := p.peek_token(3)
tok4 := p.peek_token(4) tok4 := p.peek_token(4)
tok5 := p.peek_token(5) tok5 := p.peek_token(5)
// use heuristics to detect `func<T>()` from `var < expr` kind2, kind3, kind4, kind5 := tok2.kind, tok3.kind, tok4.kind, tok5.kind
return !lit0_is_capital && p.peek_tok.kind == .lt && (match tok2.kind {
.name {
// (`f<int>`, `f<string,`) || (`f<mod.Type>`, `<mod.Type,`) || `f<map[`,
tok3.kind in [.gt, .comma] || (tok3.kind == .dot && tok4.kind == .name && tok5.kind in [.gt, .comma]) || (tok2.lit == 'map' && tok3.kind == .lsbr) if kind2 == .lsbr {
// case 1
return tok3.kind == .rsbr
}
if kind2 == .name {
if tok2.lit == 'map' && kind3 == .lsbr {
// case 2
return true
} }
.lsbr { return match kind3 {
// maybe `f<[]T>`, assume `var < []` is invalid .gt { true } // case 3
tok3.kind == .rsbr .comma { p.is_typename(tok2) } // case 4
// case 5 and 6
.dot { kind4 == .name && (kind5 == .gt || (kind5 == .comma && p.is_typename(tok4))) }
else { false }
} }
else { }
false return false
}
})
} }
pub fn (mut p Parser) name_expr() ast.Expr { pub fn (mut p Parser) name_expr() ast.Expr {

View File

@ -410,3 +410,21 @@ fn test_generic_init() {
c.name = 'c' c.name = 'c'
assert c.name == 'c' assert c.name == 'c'
} }
fn test_generic_detection() {
v1, v2 := -1, 1
// not generic
a1, a2 := v1<v2, v2> v1
assert a1 && a2
b1, b2 := v1 <simplemodule.zero, v2> v1
assert b1 && b2
// generic
assert multi_generic_args<int, string>(0, 's')
assert multi_generic_args<Foo1, Foo2>(Foo1{}, Foo2{})
assert multi_generic_args<simplemodule.Data, int>(simplemodule.Data{}, 0)
assert multi_generic_args<int, simplemodule.Data>(0, simplemodule.Data{})
assert multi_generic_args<[]int, int>([]int{}, 0)
assert multi_generic_args<map[int]int, int>(map[int]int{}, 0)
}

View File

@ -16,3 +16,5 @@ pub struct Data {
pub: pub:
value int value int
} }
pub const zero = 0