mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
generic functions
This commit is contained in:

committed by
Alexander Medvednikov

parent
f63e24e51d
commit
8a31ee4b53
@ -59,9 +59,9 @@ mut:
|
||||
calling_c bool
|
||||
cur_fn Fn
|
||||
local_vars []Var // local function variables
|
||||
var_idx int
|
||||
returns bool
|
||||
vroot string
|
||||
var_idx int
|
||||
returns bool
|
||||
vroot string
|
||||
is_c_struct_init bool
|
||||
is_empty_c_struct_init bool
|
||||
is_c_fn_call bool
|
||||
@ -71,7 +71,7 @@ mut:
|
||||
var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization
|
||||
is_alloc bool // Whether current expression resulted in an allocation
|
||||
is_const_literal bool // `1`, `2.0` etc, so that `u64_var == 0` works
|
||||
cur_gen_type string // "App" to replace "T" in current generic function
|
||||
in_dispatch bool // dispatching generic instance?
|
||||
is_vweb bool
|
||||
is_sql bool
|
||||
is_js bool
|
||||
@ -671,7 +671,11 @@ fn (p mut Parser) struct_decl() {
|
||||
}
|
||||
mut typ := p.table.find_type(name)
|
||||
if p.pass == .decl && p.table.known_type_fast(typ) {
|
||||
p.error('`$name` redeclared')
|
||||
if name in reserved_type_param_names {
|
||||
p.error('name `$name` is reserved for type parameters')
|
||||
} else {
|
||||
p.error('type `$name` redeclared')
|
||||
}
|
||||
}
|
||||
if is_objc {
|
||||
// Forward declaration of an Objective-C interface with `@class` :)
|
||||
@ -1030,7 +1034,8 @@ fn (p mut Parser) get_type() string {
|
||||
p.register_map(typ)
|
||||
return typ
|
||||
}
|
||||
//
|
||||
|
||||
// ptr/ref
|
||||
mut warn := false
|
||||
for p.tok == .mul {
|
||||
if p.first_pass() {
|
||||
@ -1045,7 +1050,16 @@ fn (p mut Parser) get_type() string {
|
||||
nr_muls++
|
||||
p.check(.amp)
|
||||
}
|
||||
typ += p.lit
|
||||
|
||||
// Generic type check
|
||||
ti := p.cur_fn.dispatch_of.inst
|
||||
if p.lit in ti.keys() {
|
||||
typ += ti[p.lit]
|
||||
// println('cur dispatch: $p.lit => $typ')
|
||||
} else {
|
||||
typ += p.lit
|
||||
}
|
||||
|
||||
if !p.is_struct_init {
|
||||
// Otherwise we get `foo := FooFoo{` because `Foo` was already
|
||||
// generated in name_expr()
|
||||
@ -1164,8 +1178,7 @@ fn (p mut Parser) statements_no_rcbr() string {
|
||||
mut last_st_typ := ''
|
||||
for p.tok != .rcbr && p.tok != .eof && p.tok != .key_case &&
|
||||
p.tok != .key_default && p.peek() != .arrow {
|
||||
// println(p.tok.str())
|
||||
// p.print_tok()
|
||||
// println('stm: '+p.tok.str()+', next: '+p.peek().str())
|
||||
last_st_typ = p.statement(true)
|
||||
// println('last st typ=$last_st_typ')
|
||||
if !p.inside_if_expr {
|
||||
@ -1186,7 +1199,6 @@ fn (p mut Parser) statements_no_rcbr() string {
|
||||
// p.check(.rcbr)
|
||||
}
|
||||
//p.fmt_dec()
|
||||
// println('close scope line=$p.scanner.line_nr')
|
||||
|
||||
p.close_scope()
|
||||
return last_st_typ
|
||||
@ -1255,7 +1267,9 @@ fn (p mut Parser) statement(add_semi bool) string {
|
||||
if p.returns && !p.is_vweb {
|
||||
p.error('unreachable code')
|
||||
}
|
||||
p.cgen.is_tmp = false
|
||||
// if !p.in_dispatch {
|
||||
p.cgen.is_tmp = false
|
||||
// }
|
||||
tok := p.tok
|
||||
mut q := ''
|
||||
switch tok {
|
||||
@ -1719,73 +1733,43 @@ fn (p mut Parser) name_expr() string {
|
||||
// known_type := p.table.known_type(name)
|
||||
orig_name := name
|
||||
is_c := name == 'C' && p.peek() == .dot
|
||||
mut is_c_struct_init := is_c && ptr// a := &C.mycstruct{}
|
||||
|
||||
if is_c {
|
||||
p.next()
|
||||
p.check(.name)
|
||||
p.check(.dot)
|
||||
name = p.lit
|
||||
p.fgen(name)
|
||||
// Currently struct init is set to true only we have `&C.Foo{}`, handle `C.Foo{}`:
|
||||
if !is_c_struct_init && p.peek() == .lcbr {
|
||||
is_c_struct_init = true
|
||||
// C struct initialization
|
||||
if p.peek() == .lcbr && p.table.known_type(name) {
|
||||
return p.get_struct_type(name, true, ptr)
|
||||
}
|
||||
// C function
|
||||
if p.peek() == .lpar {
|
||||
return p.get_c_func_type(name)
|
||||
}
|
||||
// C const (`C.GLFW_KEY_LEFT`)
|
||||
p.gen(name)
|
||||
p.next()
|
||||
return 'int'
|
||||
}
|
||||
|
||||
// enum value? (`color == .green`)
|
||||
if p.tok == .dot {
|
||||
//println('got enum dot val $p.left_type pass=$p.pass $p.scanner.line_nr left=$p.left_type')
|
||||
T := p.find_type(p.expected_type)
|
||||
if T.cat == .enum_ {
|
||||
p.check(.dot)
|
||||
val := p.check_name()
|
||||
// Make sure this enum value exists
|
||||
if !T.has_enum_val(val) {
|
||||
p.error('enum `$T.name` does not have value `$val`')
|
||||
}
|
||||
p.gen(mod_gen_name(T.mod) + '__' + p.expected_type + '_' + val)
|
||||
if p.table.known_type(p.expected_type) {
|
||||
p.check_enum_member_access()
|
||||
// println("found enum value: $p.expected_type")
|
||||
return p.expected_type
|
||||
} else {
|
||||
p.error("unknown enum: `$p.expected_type`")
|
||||
}
|
||||
return p.expected_type
|
||||
}
|
||||
|
||||
// Variable, checked before modules, so module shadowing is allowed.
|
||||
// (`gg = gg.newcontext(); gg.draw_rect(...)`)
|
||||
for { // TODO remove
|
||||
mut v := p.find_var_check_new_var(name) or { break }
|
||||
if name == '_' {
|
||||
p.error('cannot use `_` as value')
|
||||
if p.known_var_check_new_var(name) {
|
||||
rtyp := p.get_var_type(name, ptr, deref)
|
||||
return rtyp
|
||||
}
|
||||
if ptr {
|
||||
p.gen('&')
|
||||
}
|
||||
else if deref {
|
||||
p.gen('*')
|
||||
}
|
||||
if p.pref.autofree && v.typ == 'string' && v.is_arg &&
|
||||
p.assigned_type == 'string' {
|
||||
p.warn('setting moved ' + v.typ)
|
||||
p.mark_arg_moved(v)
|
||||
}
|
||||
mut typ := p.var_expr(v)
|
||||
// *var
|
||||
if deref {
|
||||
if !typ.contains('*') && !typ.ends_with('ptr') {
|
||||
println('name="$name", t=$v.typ')
|
||||
p.error('dereferencing requires a pointer, but got `$typ`')
|
||||
}
|
||||
typ = typ.replace('ptr', '')// TODO
|
||||
typ = typ.replace('*', '')// TODO
|
||||
}
|
||||
// &var
|
||||
else if ptr {
|
||||
typ += '*'
|
||||
}
|
||||
if p.inside_return_expr {
|
||||
//println('marking $v.name returned')
|
||||
p.mark_var_returned(v)
|
||||
// v.is_returned = true // TODO modifying a local variable
|
||||
// that's not used afterwards, this should be a compilation
|
||||
// error
|
||||
}
|
||||
return typ
|
||||
} // TODO REMOVE for{}
|
||||
|
||||
// Module?
|
||||
if p.peek() == .dot && ((name == p.mod && p.table.known_mod(name)) ||
|
||||
p.import_table.known_alias(name)) && !is_c {
|
||||
@ -1801,6 +1785,7 @@ fn (p mut Parser) name_expr() string {
|
||||
p.fgen(name)
|
||||
name = prepend_mod(mod_gen_name(mod), name)
|
||||
}
|
||||
|
||||
// Unknown name, try prepending the module name to it
|
||||
// TODO perf
|
||||
else if !p.table.known_type(name) &&
|
||||
@ -1809,52 +1794,15 @@ fn (p mut Parser) name_expr() string {
|
||||
name = p.prepend_mod(name)
|
||||
}
|
||||
|
||||
// Variable, checked before modules, so module shadowing is allowed.
|
||||
// (`gg = gg.newcontext(); gg.draw_rect(...)`)
|
||||
for { // TODO remove
|
||||
mut v := p.find_var_check_new_var(name) or { break }
|
||||
if name == '_' {
|
||||
p.error('cannot use `_` as value')
|
||||
// re-check
|
||||
if p.known_var_check_new_var(name) {
|
||||
return p.get_var_type(name, ptr, deref)
|
||||
}
|
||||
if ptr {
|
||||
p.gen('&')
|
||||
}
|
||||
else if deref {
|
||||
p.gen('*')
|
||||
}
|
||||
if p.pref.autofree && v.typ == 'string' && v.is_arg &&
|
||||
p.assigned_type == 'string' {
|
||||
p.warn('setting moved ' + v.typ)
|
||||
p.mark_arg_moved(v)
|
||||
}
|
||||
mut typ := p.var_expr(v)
|
||||
// *var
|
||||
if deref {
|
||||
if !typ.contains('*') && !typ.ends_with('ptr') {
|
||||
println('name="$name", t=$v.typ')
|
||||
p.error('dereferencing requires a pointer, but got `$typ`')
|
||||
}
|
||||
typ = typ.replace('ptr', '')// TODO
|
||||
typ = typ.replace('*', '')// TODO
|
||||
}
|
||||
// &var
|
||||
else if ptr {
|
||||
typ += '*'
|
||||
}
|
||||
if p.inside_return_expr {
|
||||
//println('marking $v.name returned')
|
||||
p.mark_var_returned(v)
|
||||
// v.is_returned = true // TODO modifying a local variable
|
||||
// that's not used afterwards, this should be a compilation
|
||||
// error
|
||||
}
|
||||
return typ
|
||||
} // TODO REMOVE for{}
|
||||
|
||||
// if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) {
|
||||
// known type? int(4.5) or Color.green (enum)
|
||||
if p.table.known_type(name) {
|
||||
// float(5), byte(0), (*int)(ptr) etc
|
||||
// cast expression: float(5), byte(0), (*int)(ptr) etc
|
||||
if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) {
|
||||
if deref {
|
||||
name += '*'
|
||||
@ -1885,87 +1833,20 @@ fn (p mut Parser) name_expr() string {
|
||||
p.next()
|
||||
return enum_type.name
|
||||
}
|
||||
// struct initialization
|
||||
// normal struct init (non-C)
|
||||
else if p.peek() == .lcbr {
|
||||
if ptr {
|
||||
name += '*' // `&User{}` => type `User*`
|
||||
}
|
||||
if name == 'T' {
|
||||
name = p.cur_gen_type
|
||||
}
|
||||
p.is_c_struct_init = is_c_struct_init
|
||||
return p.struct_init(name)
|
||||
return p.get_struct_type(name, false, ptr)
|
||||
}
|
||||
}
|
||||
if is_c {
|
||||
// C const (`C.GLFW_KEY_LEFT`)
|
||||
if p.peek() != .lpar {
|
||||
p.gen(name)
|
||||
p.next()
|
||||
return 'int'
|
||||
}
|
||||
// C function
|
||||
f := Fn {
|
||||
name: name
|
||||
is_c: true
|
||||
}
|
||||
p.is_c_fn_call = true
|
||||
p.fn_call(f, 0, '', '')
|
||||
p.is_c_fn_call = false
|
||||
// Try looking it up. Maybe its defined with "C.fn_name() fn_type",
|
||||
// then we know what type it returns
|
||||
cfn := p.table.find_fn(name) or {
|
||||
// Not Found? Return 'void*'
|
||||
//return 'cvoid' //'void*'
|
||||
if false {
|
||||
p.warn('\ndefine imported C function with ' +
|
||||
'`fn C.$name([args]) [return_type]`\n')
|
||||
}
|
||||
return 'void*'
|
||||
}
|
||||
return cfn.typ
|
||||
}
|
||||
|
||||
// Constant
|
||||
for {
|
||||
c := p.table.find_const(name) or { break }
|
||||
if ptr && !c.is_global {
|
||||
p.error('cannot take the address of constant `$c.name`')
|
||||
} else if ptr && c.is_global {
|
||||
// c.ptr = true
|
||||
p.gen('& /*const*/ ')
|
||||
}
|
||||
mut typ := p.var_expr(c)
|
||||
if ptr {
|
||||
typ += '*'
|
||||
}
|
||||
return typ
|
||||
if p.table.known_const(name) {
|
||||
return p.get_const_type(name, ptr)
|
||||
}
|
||||
// Function (not method btw, methods are handled in `dot()`)
|
||||
|
||||
// Function (not method btw, methods are handled in dot())
|
||||
mut f := p.table.find_fn_is_script(name, p.v_script) or {
|
||||
// We are in the second pass, that means this function was not defined,
|
||||
// throw an error.
|
||||
if !p.first_pass() {
|
||||
// check for misspelled function / variable / module
|
||||
suggested := p.identify_typo(name, p.import_table)
|
||||
if suggested != '' {
|
||||
p.error('undefined: `$name`. did you mean:$suggested')
|
||||
}
|
||||
// If orig_name is a mod, then printing undefined: `mod` tells us nothing
|
||||
if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) {
|
||||
name = name.replace('__', '.')
|
||||
p.error('undefined: `$name`')
|
||||
}
|
||||
else {
|
||||
p.error('undefined: `$orig_name`')
|
||||
}
|
||||
} else {
|
||||
p.next()
|
||||
// First pass, the function can be defined later.
|
||||
// Only in const definitions? (since fn bodies are skipped
|
||||
// in the first pass).
|
||||
return 'void'
|
||||
}
|
||||
return 'void'
|
||||
return p.get_undefined_fn_type(name, orig_name)
|
||||
}
|
||||
// no () after func, so func is an argument, just gen its name
|
||||
// TODO verify this and handle errors
|
||||
@ -1986,8 +1867,20 @@ fn (p mut Parser) name_expr() string {
|
||||
if f.typ == 'void' && !p.inside_if_expr {
|
||||
// p.error('`$f.name` used as value')
|
||||
}
|
||||
//p.log('calling function')
|
||||
p.fn_call(f, 0, '', '')
|
||||
|
||||
// println('call to fn $f.name of type $f.typ')
|
||||
// TODO replace the following dirty hacks (needs ptr access to fn table)
|
||||
new_f := f
|
||||
p.fn_call(mut new_f, 0, '', '')
|
||||
if f.is_generic {
|
||||
f2 := p.table.find_fn(f.name) or {
|
||||
return ''
|
||||
}
|
||||
// println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ')
|
||||
// println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst')
|
||||
}
|
||||
f = new_f
|
||||
|
||||
// dot after a function call: `get_user().age`
|
||||
if p.tok == .dot {
|
||||
mut typ := ''
|
||||
@ -2005,6 +1898,146 @@ fn (p mut Parser) name_expr() string {
|
||||
return f.typ
|
||||
}
|
||||
|
||||
fn (p mut Parser) get_struct_type(name_ string, is_c bool, is_ptr bool) string {
|
||||
mut name := name_
|
||||
if is_ptr {
|
||||
name += '*' // `&User{}` => type `User*`
|
||||
}
|
||||
if name in reserved_type_param_names {
|
||||
p.warn('name `$name` is reserved for type parameters')
|
||||
}
|
||||
p.is_c_struct_init = is_c
|
||||
return p.struct_init(name)
|
||||
}
|
||||
|
||||
fn (p mut Parser) check_enum_member_access() {
|
||||
T := p.find_type(p.expected_type)
|
||||
if T.cat == .enum_ {
|
||||
p.check(.dot)
|
||||
val := p.check_name()
|
||||
// Make sure this enum value exists
|
||||
if !T.has_enum_val(val) {
|
||||
p.error('enum `$T.name` does not have value `$val`')
|
||||
}
|
||||
p.gen(mod_gen_name(T.mod) + '__' + p.expected_type + '_' + val)
|
||||
} else {
|
||||
p.error('`$T.name` is not an enum')
|
||||
}
|
||||
}
|
||||
|
||||
fn (p mut Parser) get_var_type(name string, is_ptr bool, is_deref bool) string {
|
||||
v := p.find_var_check_new_var(name) or { return "" }
|
||||
if name == '_' {
|
||||
p.error('cannot use `_` as value')
|
||||
}
|
||||
if is_ptr {
|
||||
p.gen('&')
|
||||
}
|
||||
else if is_deref {
|
||||
p.gen('*')
|
||||
}
|
||||
if p.pref.autofree && v.typ == 'string' && v.is_arg &&
|
||||
p.assigned_type == 'string' {
|
||||
p.warn('setting moved ' + v.typ)
|
||||
p.mark_arg_moved(v)
|
||||
}
|
||||
mut typ := p.var_expr(v)
|
||||
// *var
|
||||
if is_deref {
|
||||
if !typ.contains('*') && !typ.ends_with('ptr') {
|
||||
println('name="$name", t=$v.typ')
|
||||
p.error('dereferencing requires a pointer, but got `$typ`')
|
||||
}
|
||||
typ = typ.replace('ptr', '')// TODO
|
||||
typ = typ.replace('*', '')// TODO
|
||||
}
|
||||
// &var
|
||||
else if is_ptr {
|
||||
typ += '*'
|
||||
}
|
||||
if p.inside_return_expr {
|
||||
//println('marking $v.name returned')
|
||||
p.mark_var_returned(v)
|
||||
// v.is_returned = true // TODO modifying a local variable
|
||||
// that's not used afterwards, this should be a compilation
|
||||
// error
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
fn (p mut Parser) get_const_type(name string, is_ptr bool) string {
|
||||
c := p.table.find_const(name) or { return "" }
|
||||
if is_ptr && !c.is_global {
|
||||
p.error('cannot take the address of constant `$c.name`')
|
||||
} else if is_ptr && c.is_global {
|
||||
// c.ptr = true
|
||||
p.gen('& /*const*/ ')
|
||||
}
|
||||
mut typ := p.var_expr(c)
|
||||
if is_ptr {
|
||||
typ += '*'
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
fn (p mut Parser) get_c_func_type(name string) string {
|
||||
f := Fn {
|
||||
name: name
|
||||
is_c: true
|
||||
}
|
||||
p.is_c_fn_call = true
|
||||
p.fn_call(mut f, 0, '', '')
|
||||
p.is_c_fn_call = false
|
||||
// Try looking it up. Maybe its defined with "C.fn_name() fn_type",
|
||||
// then we know what type it returns
|
||||
cfn := p.table.find_fn(name) or {
|
||||
// Not Found? Return 'void*'
|
||||
//return 'cvoid' //'void*'
|
||||
if false {
|
||||
p.warn('\ndefine imported C function with ' +
|
||||
'`fn C.$name([args]) [return_type]`\n')
|
||||
}
|
||||
return 'void*'
|
||||
}
|
||||
// println("C fn $name has type $cfn.typ")
|
||||
return cfn.typ
|
||||
}
|
||||
|
||||
fn (p mut Parser) get_undefined_fn_type(name string, orig_name string) string {
|
||||
if p.first_pass() {
|
||||
p.next()
|
||||
// First pass, the function can be defined later.
|
||||
return 'void'
|
||||
} else {
|
||||
// We are in the second pass, that means this function was not defined, throw an error.
|
||||
|
||||
// V script? Try os module.
|
||||
// TODO
|
||||
if p.v_script {
|
||||
//name = name.replace('main__', 'os__')
|
||||
//f = p.table.find_fn(name)
|
||||
}
|
||||
|
||||
// check for misspelled function / variable / module
|
||||
suggested := p.identify_typo(name, p.import_table)
|
||||
if suggested != '' {
|
||||
p.error('undefined function: `$name`. did you mean: `$suggested`')
|
||||
}
|
||||
|
||||
// If orig_name is a mod, then printing undefined: `mod` tells us nothing
|
||||
// if p.table.known_mod(orig_name) {
|
||||
if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) {
|
||||
m_name := mod_gen_name_rev(name.replace('__', '.'))
|
||||
p.error('undefined function: `$m_name` (in module `$orig_name`)')
|
||||
} else if orig_name in reserved_type_param_names {
|
||||
p.error('the letter `$orig_name` is reserved for type parameters')
|
||||
} else {
|
||||
p.error('undefined symbol: `$orig_name`')
|
||||
}
|
||||
return 'void'
|
||||
}
|
||||
}
|
||||
|
||||
fn (p mut Parser) var_expr(v Var) string {
|
||||
//p.log('\nvar_expr() v.name="$v.name" v.typ="$v.typ"')
|
||||
// println('var expr is_tmp=$p.cgen.is_tmp\n')
|
||||
@ -2088,11 +2121,6 @@ fn (p mut Parser) var_expr(v Var) string {
|
||||
return typ
|
||||
}
|
||||
|
||||
// for debugging only
|
||||
fn (p &Parser) fileis(s string) bool {
|
||||
return p.scanner.file_path.contains(s)
|
||||
}
|
||||
|
||||
// user.name => `str_typ` is `User`
|
||||
// user.company.name => `str_typ` is `Company`
|
||||
fn (p mut Parser) dot(str_typ_ string, method_ph int) string {
|
||||
@ -2203,7 +2231,7 @@ struct $typ.name {
|
||||
p.error_with_token_index('could not find method `$field_name`', fname_tidx) // should never happen
|
||||
exit(1)
|
||||
}
|
||||
p.fn_call(method, method_ph, '', str_typ)
|
||||
p.fn_call(mut method, method_ph, '', str_typ)
|
||||
// Methods returning `array` should return `array_string`
|
||||
if method.typ == 'array' && typ.name.starts_with('array_') {
|
||||
return typ.name
|
||||
@ -2404,6 +2432,11 @@ struct IndexCfg {
|
||||
|
||||
}
|
||||
|
||||
// for debugging only
|
||||
fn (p &Parser) fileis(s string) bool {
|
||||
return p.scanner.file_path.contains(s)
|
||||
}
|
||||
|
||||
// in and dot have higher priority than `!`
|
||||
fn (p mut Parser) indot_expr() string {
|
||||
ph := p.cgen.add_placeholder()
|
||||
@ -3905,6 +3938,7 @@ fn (p mut Parser) return_st() {
|
||||
is_none := p.tok == .key_none
|
||||
p.expected_type = p.cur_fn.typ
|
||||
mut expr_type := p.bool_expression()
|
||||
// println('$p.cur_fn.name returns type $expr_type, should be $p.cur_fn.typ')
|
||||
mut types := []string
|
||||
mut mr_values := [p.cgen.cur_line.right(ph).trim_space()]
|
||||
types << expr_type
|
||||
|
Reference in New Issue
Block a user