From dcb97db804c48a292ee7ec23edca5d4ad1e4d7f2 Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Wed, 20 Mar 2024 23:24:32 +0100 Subject: [PATCH 1/6] chore: fix typos (#44) --- editors/code/src/welcome.ts | 2 +- tests/{documenttion.v => documentation.v} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{documenttion.v => documentation.v} (100%) diff --git a/editors/code/src/welcome.ts b/editors/code/src/welcome.ts index d8a1f67a..be6033b7 100644 --- a/editors/code/src/welcome.ts +++ b/editors/code/src/welcome.ts @@ -75,7 +75,7 @@ export class WelcomePanel { this.update(); // Listen for when the panel is disposed - // This happens when the user closes the panel or when the panel is closed programatically + // This happens when the user closes the panel or when the panel is closed programmatically this.panel.onDidDispose(() => this.dispose(), null, this.disposables); } diff --git a/tests/documenttion.v b/tests/documentation.v similarity index 100% rename from tests/documenttion.v rename to tests/documentation.v From 55c55c4652636bff47c766ac78d61a0ab4852a0a Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Thu, 21 Mar 2024 07:34:41 +0100 Subject: [PATCH 2/6] test: update testdata (#45) --- tests/testdata/benchmarks/checker.v | 1678 ++++++++++++++--------- tests/testdata/benchmarks/inlay_hints.v | 410 ++++-- 2 files changed, 1330 insertions(+), 758 deletions(-) diff --git a/tests/testdata/benchmarks/checker.v b/tests/testdata/benchmarks/checker.v index 96fe8810..3135705b 100644 --- a/tests/testdata/benchmarks/checker.v +++ b/tests/testdata/benchmarks/checker.v @@ -1,9 +1,9 @@ -// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license that can be found in the LICENSE file. module checker import os -import time +import strconv import v.ast import v.vmod import v.token @@ -12,7 +12,8 @@ import v.util import v.util.version import v.errors import v.pkgconfig -import v.checker.constants +import v.transformer +import v.comptime const int_min = int(0x80000000) const int_max = int(0x7FFFFFFF) @@ -20,81 +21,97 @@ const int_max = int(0x7FFFFFFF) const expr_level_cutoff_limit = 40 const stmt_level_cutoff_limit = 40 const iface_level_cutoff_limit = 100 +const generic_fn_cutoff_limit_per_fn = 10_000 // how many times post_process_generic_fns, can visit the same function before bailing out +const generic_fn_postprocess_iterations_cutoff_limit = 1000_000 + +// array_builtin_methods contains a list of all methods on array, that return other typed arrays, +// i.e. that act as *pseudogeneric* methods, that need compiler support, so that the types of the results +// are properly checked. +// Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods. pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', - 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop', 'delete'] + 'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', + 'pop', 'delete'] pub const array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods) // TODO: remove `byte` from this list when it is no longer supported pub const reserved_type_names = ['byte', 'bool', 'char', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', - 'u32', 'u64', 'f32', 'f64', 'map', 'string', 'rune', 'usize', 'isize', 'voidptr'] + 'u32', 'u64', 'f32', 'f64', 'map', 'string', 'rune', 'usize', 'isize', 'voidptr', 'thread'] pub const reserved_type_names_chk = token.new_keywords_matcher_from_array_trie(reserved_type_names) pub const vroot_is_deprecated_message = '@VROOT is deprecated, use @VMODROOT or @VEXEROOT instead' @[heap; minify] pub struct Checker { - pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct pub mut: - table &ast.Table = unsafe { nil } - file &ast.File = unsafe { nil } - nr_errors int - nr_warnings int - nr_notices int - errors []errors.Error - warnings []errors.Warning - notices []errors.Notice - error_lines []int // to avoid printing multiple errors for the same line - expected_type ast.Type - expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type - expected_expr_type ast.Type // if/match is_expr: expected_type - mod string // current module name - const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations - const_deps []string - const_names []string - global_names []string - locked_names []string // vars that are currently locked - rlocked_names []string // vars that are currently read-locked - in_for_count int // if checker is currently in a for loop - should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time. - returns bool - scope_returns bool - is_builtin_mod bool // true inside the 'builtin', 'os' or 'strconv' modules; TODO: remove the need for special casing this - is_just_builtin_mod bool // true only inside 'builtin' - is_generated bool // true for `[generated] module xyz` .v files - inside_unsafe bool // true inside `unsafe {}` blocks - inside_const bool // true inside `const ( ... )` blocks - inside_anon_fn bool // true inside `fn() { ... }()` - inside_ref_lit bool // true inside `a := &something` - inside_defer bool // true inside `defer {}` blocks - inside_fn_arg bool // `a`, `b` in `a.f(b)` - inside_ct_attr bool // true inside `[if expr]` - inside_x_is_type bool // true inside the Type expression of `if x is Type {` - inside_comptime_for_field bool - skip_flags bool // should `#flag` and `#include` be skipped - fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc - smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo - smartcast_cond_pos token.Pos // match cond - ct_cond_stack []ast.Expr + pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct + // + table &ast.Table = unsafe { nil } + file &ast.File = unsafe { nil } + // + nr_errors int + nr_warnings int + nr_notices int + errors []errors.Error + warnings []errors.Warning + notices []errors.Notice + error_lines map[string]bool // dedup errors + warning_lines map[string]bool // dedup warns + notice_lines map[string]bool // dedup notices + error_details []string + should_abort bool // when too many errors/warnings/notices are accumulated, .should_abort becomes true. It is checked in statement/expression loops, so the checker can return early, instead of wasting time. + // + expected_type ast.Type + expected_or_type ast.Type // fn() or { 'this type' } eg. string. expected or block type + expected_expr_type ast.Type // if/match is_expr: expected_type + mod string // current module name + const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations + const_deps []string + const_names []string + global_names []string + locked_names []string // vars that are currently locked + rlocked_names []string // vars that are currently read-locked + in_for_count int // if checker is currently in a for loop + returns bool + scope_returns bool + is_builtin_mod bool // true inside the 'builtin', 'os' or 'strconv' modules; TODO: remove the need for special casing this + is_just_builtin_mod bool // true only inside 'builtin' + is_generated bool // true for `[generated] module xyz` .v files + inside_unsafe bool // true inside `unsafe {}` blocks + inside_const bool // true inside `const ( ... )` blocks + inside_anon_fn bool // true inside `fn() { ... }()` + inside_lambda bool // true inside `|...| ...` + inside_ref_lit bool // true inside `a := &something` + inside_defer bool // true inside `defer {}` blocks + inside_return bool // true inside `return ...` blocks + inside_fn_arg bool // `a`, `b` in `a.f(b)` + inside_ct_attr bool // true inside `[if expr]` + inside_x_is_type bool // true inside the Type expression of `if x is Type {` + inside_generic_struct_init bool + cur_struct_generic_types []ast.Type + cur_struct_concrete_types []ast.Type + skip_flags bool // should `#flag` and `#include` be skipped + fn_level int // 0 for the top level, 1 for `fn abc() {}`, 2 for a nested fn, etc + smartcast_mut_pos token.Pos // match mut foo, if mut foo is Foo + smartcast_cond_pos token.Pos // match cond + ct_cond_stack []ast.Expr + ct_user_defines map[string]ComptimeBranchSkipState + ct_system_defines map[string]ComptimeBranchSkipState mut: stmt_level int // the nesting level inside each stmts list; // .stmt_level is used to check for `evaluated but not used` ExprStmts like `1 << 1` // 1 for statements directly at each inner scope level; // increases for `x := if cond { statement_list1} else {statement_list2}`; // increases for `x := optfn() or { statement_list3 }`; - files []ast.File + // files []ast.File expr_level int // to avoid infinite recursion segfaults due to compiler bugs ensure_generic_type_level int // to avoid infinite recursion segfaults in ensure_generic_type_specify_type_names cur_orm_ts ast.TypeSymbol cur_anon_fn &ast.AnonFn = unsafe { nil } - error_details []string vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path** loop_label string // set when inside a labelled for loop vweb_gen_types []ast.Type // vweb route checks timers &util.Timers = util.get_timers() - comptime_for_field_var string - comptime_fields_default_type ast.Type - comptime_fields_type map[string]ast.Type - comptime_for_field_value ast.StructField // value of the field variable - comptime_enum_field_value string // current enum value name + comptime_info_stack []comptime.ComptimeInfo // stores the values from the above on each $for loop, to make nesting them easier + comptime comptime.ComptimeInfo fn_scope &ast.Scope = unsafe { nil } main_fn_decl_node ast.FnDecl match_exhaustive_cutoff_limit int = 10 @@ -104,14 +121,22 @@ mut: need_recheck_generic_fns bool // need recheck generic fns because there are cascaded nested generic fn inside_sql bool // to handle sql table fields pseudo variables inside_selector_expr bool - inside_println_arg bool + inside_interface_deref bool inside_decl_rhs bool inside_if_guard bool // true inside the guard condition of `if x := opt() {}` inside_assign bool - is_index_assign bool - comptime_call_pos int // needed for correctly checking use before decl for templates - goto_labels map[string]ast.GotoLabel // to check for unused goto labels - enum_data_type ast.Type + // doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect) + // doing_line_path string // same, but stores the path being parsed + is_index_assign bool + comptime_call_pos int // needed for correctly checking use before decl for templates + goto_labels map[string]ast.GotoLabel // to check for unused goto labels + enum_data_type ast.Type + field_data_type ast.Type + variant_data_type ast.Type + fn_return_type ast.Type + orm_table_fields map[string][]ast.StructField // known table structs + // + v_current_commit_hash string // same as old C.V_CURRENT_COMMIT_HASH } pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker { @@ -119,12 +144,18 @@ pub fn new_checker(table &ast.Table, pref_ &pref.Preferences) &Checker { $if time_checking ? { timers_should_print = true } - return &Checker{ + mut checker := &Checker{ table: table pref: pref_ timers: util.new_timers(should_print: timers_should_print, label: 'checker') match_exhaustive_cutoff_limit: pref_.checker_match_exhaustive_cutoff_limit + v_current_commit_hash: version.githash(pref_.building_v) } + checker.comptime = &comptime.ComptimeInfo{ + resolver: checker + table: table + } + return checker } fn (mut c Checker) reset_checker_state_at_start_of_new_file() { @@ -155,16 +186,30 @@ fn (mut c Checker) reset_checker_state_at_start_of_new_file() { c.loop_label = '' c.using_new_err_struct = false c.inside_selector_expr = false - c.inside_println_arg = false + c.inside_interface_deref = false c.inside_decl_rhs = false c.inside_if_guard = false + c.error_details.clear() } -pub fn (mut c Checker) check(ast_file_ &ast.File) { - mut ast_file := unsafe { ast_file_ } +pub fn (mut c Checker) check(mut ast_file ast.File) { c.reset_checker_state_at_start_of_new_file() c.change_current_file(ast_file) for i, ast_import in ast_file.imports { + // Imports with the same path and name (self-imports and module name conflicts with builtin module imports) + if c.mod == ast_import.mod { + c.error('cannot import `${ast_import.mod}` into a module with the same name', + ast_import.mod_pos) + } + // Duplicates of regular imports with the default alias (modname) and `as` imports with a custom alias + if c.mod == ast_import.alias { + if c.mod == ast_import.mod.all_after_last('.') { + c.error('cannot import `${ast_import.mod}` into a module with the same name', + ast_import.mod_pos) + } + c.error('cannot import `${ast_import.mod}` as `${ast_import.alias}` into a module with the same name', + ast_import.alias_pos) + } for sym in ast_import.syms { full_name := ast_import.mod + '.' + sym.name if full_name in c.const_names { @@ -176,6 +221,12 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) { if ast_import.mod == ast_file.imports[j].mod { c.error('`${ast_import.mod}` was already imported on line ${ ast_file.imports[j].mod_pos.line_nr + 1}', ast_import.mod_pos) + } else if ast_import.mod == ast_file.imports[j].alias { + c.error('`${ast_file.imports[j].mod}` was already imported as `${ast_import.alias}` on line ${ + ast_file.imports[j].mod_pos.line_nr + 1}', ast_import.mod_pos) + } else if ast_import.alias != '_' && ast_import.alias == ast_file.imports[j].alias { + c.error('`${ast_file.imports[j].mod}` was already imported on line ${ + ast_file.imports[j].alias_pos.line_nr + 1}', ast_import.alias_pos) } } } @@ -183,7 +234,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) { for mut stmt in ast_file.stmts { if stmt in [ast.ConstDecl, ast.ExprStmt] { c.expr_level = 0 - c.stmt(stmt) + c.stmt(mut stmt) } if c.should_abort { return @@ -194,7 +245,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) { for mut stmt in ast_file.stmts { if stmt is ast.GlobalDecl { c.expr_level = 0 - c.stmt(stmt) + c.stmt(mut stmt) } if c.should_abort { return @@ -205,7 +256,7 @@ pub fn (mut c Checker) check(ast_file_ &ast.File) { for mut stmt in ast_file.stmts { if stmt !in [ast.ConstDecl, ast.GlobalDecl, ast.ExprStmt] { c.expr_level = 0 - c.stmt(stmt) + c.stmt(mut stmt) } if c.should_abort { return @@ -222,7 +273,9 @@ pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) { match obj { ast.Var { if !obj.is_used && obj.name[0] != `_` { - c.warn('unused variable: `${obj.name}`', obj.pos) + if !c.pref.translated && !c.file.is_translated { + c.warn('unused variable: `${obj.name}`', obj.pos) + } } if obj.is_mut && !obj.is_changed && !c.is_builtin_mod && obj.name != 'it' { // if obj.is_mut && !obj.is_changed && !c.is_builtin { //TODO C error bad field not checked @@ -240,10 +293,10 @@ pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) { } // not used right now -pub fn (mut c Checker) check2(ast_file &ast.File) []errors.Error { +pub fn (mut c Checker) check2(mut ast_file ast.File) []errors.Error { c.change_current_file(ast_file) - for stmt in ast_file.stmts { - c.stmt(stmt) + for mut stmt in ast_file.stmts { + c.stmt(mut stmt) } return c.errors } @@ -256,6 +309,7 @@ pub fn (mut c Checker) change_current_file(file &ast.File) { } pub fn (mut c Checker) check_files(ast_files []&ast.File) { + // println('check_files') // c.files = ast_files mut has_main_mod_file := false mut has_main_fn := false @@ -264,7 +318,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { for i in 0 .. ast_files.len { mut file := ast_files[i] c.timers.start('checker_check ${file.path}') - c.check(file) + c.check(mut file) if file.mod.name == 'main' { files_from_main_module << file has_main_mod_file = true @@ -285,7 +339,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { file: the_main_file.path return_type: ast.void_type scope: &ast.Scope{ - parent: 0 + parent: nil } } has_main_fn = true @@ -299,7 +353,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { // is needed when the generic type is auto inferred from the call argument. // we may have to loop several times, if there were more concrete types found. mut post_process_generic_fns_iterations := 0 - for { + post_process_iterations_loop: for post_process_generic_fns_iterations <= checker.generic_fn_postprocess_iterations_cutoff_limit { $if trace_post_process_generic_fns_loop ? { eprintln('>>>>>>>>> recheck_generic_fns loop iteration: ${post_process_generic_fns_iterations}') } @@ -310,7 +364,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { file.generic_fns.map(it.name).str()) } c.change_current_file(file) - c.post_process_generic_fns() + c.post_process_generic_fns() or { break post_process_iterations_loop } } } if !c.need_recheck_generic_fns { @@ -344,6 +398,29 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { c.error('a _test.v file should have *at least* one `test_` function', token.Pos{}) } } + // After the main checker run, run the line info check, print line info, and exit (if it's present) + if !c.pref.linfo.is_running && c.pref.line_info != '' { //'' && c.pref.linfo.line_nr == 0 { + // c.do_line_info(c.pref.line_info, ast_files) + println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd()) + c.pref.linfo.is_running = true + for i, file in ast_files { + // println(file.path) + if file.path == c.pref.linfo.path { + println('running c.check_files') + c.check_files([ast_files[i]]) + exit(0) + } else if file.path.starts_with('./') { + // Maybe it's a "./foo.v", linfo.path has an absolute path + abs_path := os.join_path(os.getwd(), file.path).replace('/./', '/') // TODO join_path shouldn't have /./ + if abs_path == c.pref.linfo.path { + c.check_files([ast_files[i]]) + exit(0) + } + } + } + println('failed to find file "${c.pref.linfo.path}"') + exit(0) + } // Make sure fn main is defined in non lib builds if c.pref.build_mode == .build_module || c.pref.is_test { return @@ -397,7 +474,7 @@ fn (mut c Checker) check_valid_snake_case(name string, identifier string, pos to if c.pref.translated || c.file.is_translated { return } - if !c.pref.is_vweb && name.len > 0 && (name[0] == `_` || name.contains('._')) { + if !c.pref.is_vweb && name.len > 1 && (name[0] == `_` || name.contains('._')) { c.error('${identifier} `${name}` cannot start with `_`', pos) } if !c.pref.experimental && util.contains_capital(name) { @@ -407,7 +484,7 @@ fn (mut c Checker) check_valid_snake_case(name string, identifier string, pos to } fn stripped_name(name string) string { - idx := name.last_index('.') or { -1 } + idx := name.index_last('.') or { -1 } return name[(idx + 1)..] } @@ -430,12 +507,17 @@ fn (mut c Checker) type_decl(node ast.TypeDecl) { } fn (mut c Checker) alias_type_decl(node ast.AliasTypeDecl) { - // TODO Remove when `u8` isn't an alias in builtin anymore - if c.file.mod.name != 'builtin' { + if c.file.mod.name != 'builtin' && !node.name.starts_with('C.') { c.check_valid_pascal_case(node.name, 'type alias', node.pos) } - c.ensure_type_exists(node.parent_type, node.type_pos) or { return } + if !c.ensure_type_exists(node.parent_type, node.type_pos) { + return + } mut parent_typ_sym := c.table.sym(node.parent_type) + if node.parent_type.has_flag(.result) { + c.add_error_detail('Result types cannot be stored and have to be unwrapped immediately') + c.error('cannot make an alias of Result type', node.type_pos) + } match parent_typ_sym.kind { .placeholder, .int_literal, .float_literal { c.error('unknown aliased type `${parent_typ_sym.name}`', node.type_pos) @@ -492,6 +574,9 @@ fn (mut c Checker) alias_type_decl(node ast.AliasTypeDecl) { // type Sum = int | Alias // type Alias = Sum } + .none_ { + c.error('cannot create a type alias of `none` as it is a value', node.type_pos) + } // The rest of the parent symbol kinds are also allowed, since they are either primitive types, // that in turn do not allow recursion, or are abstract enough so that they can not be checked at comptime: else {} @@ -522,13 +607,15 @@ fn (mut c Checker) fn_type_decl(node ast.FnTypeDecl) { typ_sym := c.table.sym(node.typ) fn_typ_info := typ_sym.info as ast.FnType fn_info := fn_typ_info.func - c.ensure_type_exists(fn_info.return_type, fn_info.return_type_pos) or {} + c.ensure_type_exists(fn_info.return_type, fn_info.return_type_pos) ret_sym := c.table.sym(fn_info.return_type) if ret_sym.kind == .placeholder { c.error('unknown type `${ret_sym.name}`', fn_info.return_type_pos) } for arg in fn_info.params { - c.ensure_type_exists(arg.typ, arg.type_pos) or { return } + if !c.ensure_type_exists(arg.typ, arg.type_pos) { + return + } arg_sym := c.table.sym(arg.typ) if arg_sym.kind == .placeholder { c.error('unknown type `${arg_sym.name}`', arg.type_pos) @@ -540,11 +627,20 @@ fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) { c.check_valid_pascal_case(node.name, 'sum type', node.pos) mut names_used := []string{} for variant in node.variants { - if variant.typ.is_ptr() { + c.ensure_type_exists(variant.typ, variant.pos) + sym := c.table.sym(variant.typ) + if variant.typ.is_ptr() || (sym.info is ast.Alias && sym.info.parent_type.is_ptr()) { + variant_name := sym.name.all_after_last('.') + lb, rb := if sym.kind == .struct_ { '{', '}' } else { '(', ')' } + msg := if sym.info is ast.Alias && sym.info.parent_type.is_ptr() { + 'alias as non-reference type' + } else { + 'the sum type with non-reference types' + } + c.add_error_detail('declare ${msg}: `${node.name} = ${variant_name} | ...` +and use a reference to the sum type instead: `var := &${node.name}(${variant_name}${lb}val${rb})`') c.error('sum type cannot hold a reference type', variant.pos) } - c.ensure_type_exists(variant.typ, variant.pos) or {} - sym := c.table.sym(variant.typ) if sym.name in names_used { c.error('sum type ${node.name} cannot hold the type `${sym.name}` more than once', variant.pos) @@ -567,9 +663,9 @@ fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) { for typ in sym.info.generic_types { if typ !in node.generic_types { sumtype_type_names := node.generic_types.map(c.table.type_to_str(it)).join(', ') - generic_sumtype_name := '${node.name}<${sumtype_type_names}>' + generic_sumtype_name := '${node.name}[${sumtype_type_names}]' variant_type_names := sym.info.generic_types.map(c.table.type_to_str(it)).join(', ') - generic_variant_name := '${sym.name}<${variant_type_names}>' + generic_variant_name := '${sym.name}[${variant_type_names}]' c.error('generic type name `${c.table.sym(typ).name}` of generic struct `${generic_variant_name}` is not mentioned in sumtype `${generic_sumtype_name}`', variant.pos) } @@ -577,6 +673,16 @@ fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) { } } } else if sym.info is ast.FnType { + if sym.info.func.generic_names.len > 0 { + if !variant.typ.has_flag(.generic) { + c.error('generic fntype `${sym.name}` must specify generic type names, e.g. ${sym.name}[T]', + variant.pos) + } + if node.generic_types.len == 0 { + c.error('generic sumtype `${node.name}` must specify generic type names, e.g. ${node.name}[T]', + node.name_pos) + } + } if c.table.sym(sym.info.func.return_type).name.ends_with('.${node.name}') { c.error('sum type `${node.name}` cannot be defined recursively', variant.pos) } @@ -611,8 +717,12 @@ fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, ifac mut list := iface_decl.embeds.clone() if !iface_decl.are_embeds_expanded { list = c.expand_iface_embeds(idecl, level + 1, iface_decl.embeds) - c.table.interfaces[ie.typ].embeds = list - c.table.interfaces[ie.typ].are_embeds_expanded = true + unsafe { + c.table.interfaces[ie.typ].embeds = list + } + unsafe { + c.table.interfaces[ie.typ].are_embeds_expanded = true + } } for partial in list { res[partial.typ] = partial @@ -628,25 +738,37 @@ fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, ifac // returns name and position of variable that needs write lock // also sets `is_changed` to true (TODO update the name to reflect this?) -fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { +fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { mut to_lock := '' // name of variable that needs lock mut pos := token.Pos{} // and its position mut explicit_lock_needed := false - mut expr := unsafe { expr_ } match mut expr { ast.CastExpr { // TODO return '', expr.pos } ast.ComptimeSelector { + mut expr_left := expr.left + if mut expr.left is ast.Ident { + if mut expr.left.obj is ast.Var { + if expr.left.obj.ct_type_var != .generic_param { + c.fail_if_immutable(mut expr_left) + } + } + } return '', expr.pos } ast.Ident { if mut expr.obj is ast.Var { if !expr.obj.is_mut && !c.pref.translated && !c.file.is_translated && !c.inside_unsafe { - c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable', - expr.pos) + if c.inside_anon_fn { + c.error('the closure copy of `${expr.name}` is immutable, declare it with `mut` to make it mutable', + expr.pos) + } else { + c.error('`${expr.name}` is immutable, declare it with `mut` to make it mutable', + expr.pos) + } } expr.obj.is_changed = true if expr.obj.typ.share() == .shared_t { @@ -695,28 +817,30 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { c.error('you have to create a handle and `lock` it to modify `shared` ${kind} element', expr.left.pos().extend(expr.pos)) } - to_lock, pos = c.fail_if_immutable(expr.left) + to_lock, pos = c.fail_if_immutable(mut expr.left) } ast.ParExpr { - to_lock, pos = c.fail_if_immutable(expr.expr) + to_lock, pos = c.fail_if_immutable(mut expr.expr) } ast.PrefixExpr { if expr.op == .mul && expr.right is ast.Ident { // Do not fail if dereference is immutable: // `*x = foo()` doesn't modify `x` } else { - to_lock, pos = c.fail_if_immutable(expr.right) + to_lock, pos = c.fail_if_immutable(mut expr.right) } } ast.PostfixExpr { - to_lock, pos = c.fail_if_immutable(expr.expr) + to_lock, pos = c.fail_if_immutable(mut expr.expr) } ast.SelectorExpr { if expr.expr_type == 0 { return '', expr.pos } // retrieve ast.Field - c.ensure_type_exists(expr.expr_type, expr.pos) or { return '', expr.pos } + if !c.ensure_type_exists(expr.expr_type, expr.pos) { + return '', expr.pos + } mut typ_sym := c.table.final_sym(c.unwrap_generic(expr.expr_type)) match typ_sym.kind { .struct_ { @@ -752,7 +876,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { c.error('field `${expr.field_name}` of struct `${type_str}` is immutable', expr.pos) } - to_lock, pos = c.fail_if_immutable(expr.expr) + to_lock, pos = c.fail_if_immutable(mut expr.expr) } if to_lock != '' { // No automatic lock for struct access @@ -772,7 +896,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { expr.pos) return '', expr.pos } - c.fail_if_immutable(expr.expr) + c.fail_if_immutable(mut expr.expr) } .sum_type { sumtype_info := typ_sym.info as ast.SumType @@ -787,7 +911,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { expr.pos) return '', expr.pos } - c.fail_if_immutable(expr.expr) + c.fail_if_immutable(mut expr.expr) } .array, .string { // should only happen in `builtin` and unsafe blocks @@ -798,7 +922,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { } } .aggregate, .placeholder { - c.fail_if_immutable(expr.expr) + c.fail_if_immutable(mut expr.expr) } else { c.error('unexpected symbol `${typ_sym.kind}`', expr.pos) @@ -809,7 +933,7 @@ fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { ast.CallExpr { // TODO: should only work for builtin method if expr.name == 'slice' { - to_lock, pos = c.fail_if_immutable(expr.left) + to_lock, pos = c.fail_if_immutable(mut expr.left) if to_lock != '' { // No automatic lock for array slicing (yet(?)) explicit_lock_needed = true @@ -849,8 +973,14 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to eprintln('> type_implements typ: ${typ.debug()} (`${c.table.type_to_str(typ)}`) | inter_typ: ${interface_type.debug()} (`${c.table.type_to_str(interface_type)}`)') } utyp := c.unwrap_generic(typ) + styp := c.table.type_to_str(utyp) typ_sym := c.table.sym(utyp) mut inter_sym := c.table.sym(interface_type) + if !inter_sym.is_pub && inter_sym.mod !in [typ_sym.mod, c.mod] && typ_sym.mod != 'builtin' { + c.error('`${styp}` cannot implement private interface `${inter_sym.name}` of other module', + pos) + return false + } // small hack for JS.Any type. Since `any` in regular V is getting deprecated we have our own JS.Any type for JS backend. if typ_sym.name == 'JS.Any' { @@ -897,7 +1027,6 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to // `none` "implements" the Error interface return true } - styp := c.table.type_to_str(utyp) if typ_sym.kind == .interface_ && inter_sym.kind == .interface_ && !styp.starts_with('JS.') && !inter_sym.name.starts_with('JS.') { c.error('cannot implement interface `${inter_sym.name}` with a different interface `${styp}`', @@ -989,86 +1118,132 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to return true } -// return the actual type of the expression, once the option is handled -fn (mut c Checker) check_expr_opt_call(expr ast.Expr, ret_type ast.Type) ast.Type { - if expr is ast.CallExpr { - mut expr_ret_type := expr.return_type - if expr_ret_type != 0 && c.table.sym(expr_ret_type).kind == .alias { - unaliased_ret_type := c.table.unaliased_type(expr_ret_type) - if unaliased_ret_type.has_flag(.option) || unaliased_ret_type.has_flag(.result) { - expr_ret_type = unaliased_ret_type - } +// helper for expr_or_block_err +fn is_field_to_description(expr_name string, is_field bool) string { + return if is_field { + 'field `${expr_name}` is not' + } else { + 'function `${expr_name}` does not return' + } +} + +fn (mut c Checker) expr_or_block_err(kind ast.OrKind, expr_name string, pos token.Pos, is_field bool) { + match kind { + .absent { + // do nothing, most common case; do not be tempted to move the call to is_field_to_description above it, since that will slow it down } - if expr_ret_type.has_flag(.option) || expr_ret_type.has_flag(.result) { - return_modifier_kind := if expr_ret_type.has_flag(.option) { - 'an Option' - } else { - 'a Result' + .block { + obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field) + c.error('unexpected `or` block, the ${obj_does_not_return_or_is_not} an Option or a Result', + pos) + } + .propagate_option { + obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field) + c.error('unexpected `?`, the ${obj_does_not_return_or_is_not} an Option', + pos) + } + .propagate_result { + obj_does_not_return_or_is_not := is_field_to_description(expr_name, is_field) + c.error('unexpected `!`, the ${obj_does_not_return_or_is_not} a Result', pos) + } + } +} + +// return the actual type of the expression, once the result or option type is handled +fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast.Type) ast.Type { + match expr { + ast.CallExpr { + mut expr_ret_type := expr.return_type + if expr_ret_type != 0 && c.table.sym(expr_ret_type).kind == .alias { + unaliased_ret_type := c.table.unaliased_type(expr_ret_type) + if unaliased_ret_type.has_option_or_result() { + expr_ret_type = unaliased_ret_type + } } - return_modifier := if expr_ret_type.has_flag(.option) { '?' } else { '!' } - if expr_ret_type.has_flag(.result) && expr.or_block.kind == .absent { - if c.inside_defer { - c.error('${expr.name}() returns ${return_modifier_kind}, so it should have an `or {}` block at the end', - expr.pos) + if expr_ret_type.has_option_or_result() { + return_modifier_kind := if expr_ret_type.has_flag(.option) { + 'an Option' } else { - c.error('${expr.name}() returns ${return_modifier_kind}, so it should have either an `or {}` block, or `${return_modifier}` at the end', - expr.pos) + 'a Result' } + return_modifier := if expr_ret_type.has_flag(.option) { '?' } else { '!' } + if expr_ret_type.has_flag(.result) && expr.or_block.kind == .absent { + if c.inside_defer { + c.error('${expr.name}() returns ${return_modifier_kind}, so it should have an `or {}` block at the end', + expr.pos) + } else { + c.error('${expr.name}() returns ${return_modifier_kind}, so it should have either an `or {}` block, or `${return_modifier}` at the end', + expr.pos) + } + } else { + if expr.or_block.kind != .absent { + c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr) + } + } + return ret_type.clear_flag(.result) } else { - if expr.or_block.kind != .absent { - c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr) - } - } - return ret_type.clear_flag(.result) - } else if expr.or_block.kind == .block { - c.error('unexpected `or` block, the function `${expr.name}` does not return an Option or a Result', - expr.or_block.pos) - } else if expr.or_block.kind == .propagate_option { - c.error('unexpected `?`, the function `${expr.name}` does not return an Option', - expr.or_block.pos) - } else if expr.or_block.kind == .propagate_result { - c.error('unexpected `!`, the function `${expr.name}` does not return a Result', - expr.or_block.pos) - } - } else if expr is ast.SelectorExpr && c.table.sym(ret_type).kind != .chan { - if expr.typ.has_flag(.option) || expr.typ.has_flag(.result) { - with_modifier_kind := if expr.typ.has_flag(.option) { - 'an Option' - } else { - 'a Result' + c.expr_or_block_err(expr.or_block.kind, expr.name, expr.or_block.pos, + false) } - with_modifier := if expr.typ.has_flag(.option) { '?' } else { '!' } - if expr.typ.has_flag(.result) && expr.or_block.kind == .absent { - if c.inside_defer { - c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have an `or {}` block at the end', - expr.pos) + } + ast.SelectorExpr { + if c.table.sym(ret_type).kind != .chan { + if expr.typ.has_option_or_result() { + with_modifier_kind := if expr.typ.has_flag(.option) { + 'an Option' + } else { + 'a Result' + } + with_modifier := if expr.typ.has_flag(.option) { '?' } else { '!' } + if expr.typ.has_flag(.result) && expr.or_block.kind == .absent { + if c.inside_defer { + c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have an `or {}` block at the end', + expr.pos) + } else { + c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have either an `or {}` block, or `${with_modifier}` at the end', + expr.pos) + } + } else { + if expr.or_block.kind != .absent { + c.check_or_expr(expr.or_block, ret_type, expr.typ, expr) + } + } + return ret_type.clear_flag(.result) } else { - c.error('field `${expr.field_name}` is ${with_modifier_kind}, so it should have either an `or {}` block, or `${with_modifier}` at the end', - expr.pos) + c.expr_or_block_err(expr.or_block.kind, expr.field_name, expr.or_block.pos, + true) } - } else { - if expr.or_block.kind != .absent { - c.check_or_expr(expr.or_block, ret_type, expr.typ, expr) + } + } + ast.IndexExpr { + if expr.or_expr.kind != .absent { + mut return_none_or_error := false + if expr.or_expr.stmts.len > 0 { + last_stmt := expr.or_expr.stmts.last() + if last_stmt is ast.ExprStmt { + if c.inside_return && last_stmt.typ in [ast.none_type, ast.error_type] { + return_none_or_error = true + } + } + } + if return_none_or_error { + c.check_expr_option_or_result_call(expr.or_expr, c.table.cur_fn.return_type) + } else { + c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.result), + expr) } } - return ret_type.clear_flag(.result) - } else if expr.or_block.kind == .block { - c.error('unexpected `or` block, the field `${expr.field_name}` is neither an Option, nor a Result', - expr.or_block.pos) - } else if expr.or_block.kind == .propagate_option { - c.error('unexpected `?`, the field `${expr.field_name}` is not an Option', - expr.or_block.pos) - } else if expr.or_block.kind == .propagate_result { - c.error('unexpected `!`, Result fields are not supported', expr.or_block.pos) } - } else if expr is ast.IndexExpr { - if expr.or_expr.kind != .absent { - c.check_or_expr(expr.or_expr, ret_type, ret_type.set_flag(.result), expr) + ast.CastExpr { + c.check_expr_option_or_result_call(expr.expr, ret_type) + } + ast.AsCast { + c.check_expr_option_or_result_call(expr.expr, ret_type) + } + ast.ParExpr { + c.check_expr_option_or_result_call(expr.expr, ret_type) } - } else if expr is ast.CastExpr { - c.check_expr_opt_call(expr.expr, ret_type) - } else if expr is ast.AsCast { - c.check_expr_opt_call(expr.expr, ret_type) + else {} } return ret_type } @@ -1088,7 +1263,7 @@ fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return } if expr !is ast.Ident && !expr_return_type.has_flag(.option) { if expr_return_type.has_flag(.result) { - c.warn('propagating a Result like an Option is deprecated, use `foo()!` instead of `foo()?`', + c.error('propagating a Result like an Option is deprecated, use `foo()!` instead of `foo()?`', node.pos) } else { c.error('to propagate an Option, the call must also return an Option type', @@ -1118,22 +1293,21 @@ fn (mut c Checker) check_or_expr(node ast.OrExpr, ret_type ast.Type, expr_return // allow `f() or {}` return } - last_stmt := node.stmts.last() - c.check_or_last_stmt(last_stmt, ret_type, expr_return_type.clear_flags(.option, .result)) + mut last_stmt := node.stmts.last() + c.check_or_last_stmt(mut last_stmt, ret_type, expr_return_type.clear_option_and_result()) } -fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) { +fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, expr_return_type ast.Type) { if ret_type != ast.void_type { - match stmt { + match mut stmt { ast.ExprStmt { c.expected_type = ret_type - c.expected_or_type = ret_type.clear_flags(.option, .result) - last_stmt_typ := c.expr(stmt.expr) + c.expected_or_type = ret_type.clear_option_and_result() + last_stmt_typ := c.expr(mut stmt.expr) if last_stmt_typ.has_flag(.option) || last_stmt_typ == ast.none_type { - if stmt.expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr, ast.None] { - expected_type_name := c.table.type_to_str(ret_type.clear_flags(.option, - .result)) + if stmt.expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr, ast.None, ast.CastExpr] { + expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) got_type_name := c.table.type_to_str(last_stmt_typ) c.error('`or` block must provide a value of type `${expected_type_name}`, not `${got_type_name}`', stmt.expr.pos()) @@ -1149,23 +1323,24 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret return } if stmt.typ == ast.void_type { - if stmt.expr is ast.IfExpr { - for branch in stmt.expr.branches { + if mut stmt.expr is ast.IfExpr { + for mut branch in stmt.expr.branches { if branch.stmts.len > 0 { - c.check_or_last_stmt(branch.stmts.last(), ret_type, expr_return_type) + mut stmt_ := branch.stmts.last() + c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type) } } return - } else if stmt.expr is ast.MatchExpr { - for branch in stmt.expr.branches { + } else if mut stmt.expr is ast.MatchExpr { + for mut branch in stmt.expr.branches { if branch.stmts.len > 0 { - c.check_or_last_stmt(branch.stmts.last(), ret_type, expr_return_type) + mut stmt_ := branch.stmts.last() + c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type) } } return } - expected_type_name := c.table.type_to_str(ret_type.clear_flags(.option, - .result)) + expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) c.error('`or` block must provide a default value of type `${expected_type_name}`, or return/continue/break or call a [noreturn] function like panic(err) or exit(1)', stmt.expr.pos()) } else { @@ -1177,8 +1352,7 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret return } type_name := c.table.type_to_str(last_stmt_typ) - expected_type_name := c.table.type_to_str(ret_type.clear_flags(.option, - .result)) + expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) c.error('wrong return type `${type_name}` in the `or {}` block, expected `${expected_type_name}`', stmt.expr.pos()) } @@ -1192,25 +1366,26 @@ fn (mut c Checker) check_or_last_stmt(stmt ast.Stmt, ret_type ast.Type, expr_ret } ast.Return {} else { - expected_type_name := c.table.type_to_str(ret_type.clear_flags(.option, - .result)) + expected_type_name := c.table.type_to_str(ret_type.clear_option_and_result()) c.error('last statement in the `or {}` block should be an expression of type `${expected_type_name}` or exit parent scope', stmt.pos) } } - } else if stmt is ast.ExprStmt { - match stmt.expr { + } else if mut stmt is ast.ExprStmt { + match mut stmt.expr { ast.IfExpr { - for branch in stmt.expr.branches { + for mut branch in stmt.expr.branches { if branch.stmts.len > 0 { - c.check_or_last_stmt(branch.stmts.last(), ret_type, expr_return_type) + mut stmt_ := branch.stmts.last() + c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type) } } } ast.MatchExpr { - for branch in stmt.expr.branches { + for mut branch in stmt.expr.branches { if branch.stmts.len > 0 { - c.check_or_last_stmt(branch.stmts.last(), ret_type, expr_return_type) + mut stmt_ := branch.stmts.last() + c.check_or_last_stmt(mut stmt_, ret_type, expr_return_type) } } } @@ -1250,6 +1425,7 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { // T.name, typeof(expr).name mut name_type := 0 + mut node_expr := node.expr match mut node.expr { ast.Ident { name := node.expr.name @@ -1264,9 +1440,13 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { // so that we can get other metadata properties of the type, depending on `propertyname` (one of `name` or `idx` for now). // A better alternative would be a new `meta(x).propertyname`, that does not have a `meta(x)` case (an error), // or if it does, it should be a normal constant struct value, just filled at comptime. - c.expr(node.expr) + c.expr(mut node_expr) name_type = node.expr.typ } + ast.AsCast { + c.add_error_detail('for example `(${node.expr.expr} as ${c.table.type_to_str(node.expr.typ)}).${node.field_name}`') + c.error('indeterminate `as` cast, use parenthesis to clarity', node.expr.pos) + } else {} } if name_type > 0 { @@ -1291,15 +1471,15 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } } // evaluates comptime field. (from T.fields) - if c.check_comptime_is_field_selector(node) { - if c.check_comptime_is_field_selector_bool(node) { + if c.comptime.check_comptime_is_field_selector(node) { + if c.comptime.check_comptime_is_field_selector_bool(node) { node.expr_type = ast.bool_type return node.expr_type } } old_selector_expr := c.inside_selector_expr c.inside_selector_expr = true - mut typ := c.expr(node.expr) + mut typ := c.expr(mut node.expr) if node.expr.is_auto_deref_var() { if mut node.expr is ast.Ident { if mut node.expr.obj is ast.Var { @@ -1315,13 +1495,15 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { c.error('`${node.expr}` does not return a value', node.pos) node.expr_type = ast.void_type return ast.void_type - } else if c.inside_comptime_for_field && typ == c.enum_data_type && node.field_name == 'value' { + } else if c.comptime.inside_comptime_for && typ == c.enum_data_type + && node.field_name == 'value' { // for comp-time enum.values - node.expr_type = c.comptime_fields_type[c.comptime_for_field_var] + node.expr_type = c.comptime.type_map['${c.comptime.comptime_for_enum_var}.typ'] + node.typ = typ return node.expr_type } node.expr_type = typ - if !(node.expr is ast.Ident && (node.expr as ast.Ident).kind == .constant) { + if !(node.expr is ast.Ident && node.expr.kind == .constant) { if node.expr_type.has_flag(.option) { c.error('cannot access fields of an Option, handle the error with `or {...}` or propagate it with `?`', node.pos) @@ -1381,8 +1563,10 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { if !c.inside_unsafe { if sym.info is ast.Struct { if sym.info.is_union && node.next_token !in token.assign_tokens { - c.warn('reading a union field (or its address) requires `unsafe`', - node.pos) + if !c.pref.translated && !c.file.is_translated { + c.warn('reading a union field (or its address) requires `unsafe`', + node.pos) + } } } } @@ -1403,6 +1587,7 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { ast.StructField{}, []ast.Type{} } node.from_embed_types = embed_types + node.generic_from_embed_types << embed_types } } } @@ -1440,14 +1625,14 @@ fn (mut c Checker) selector_expr(mut node ast.SelectorExpr) ast.Type { } node.typ = field.typ if node.or_block.kind == .block { - c.expected_or_type = node.typ.clear_flags(.option, .result) - c.stmts_ending_with_expression(node.or_block.stmts) + c.expected_or_type = node.typ.clear_option_and_result() + c.stmts_ending_with_expression(mut node.or_block.stmts) c.check_or_expr(node.or_block, node.typ, c.expected_or_type, node) c.expected_or_type = ast.void_type } return field.typ } - if mut method := c.table.find_method(sym, field_name) { + if mut method := sym.find_method_with_generic_parent(field_name) { if c.expected_type != 0 && c.expected_type != ast.none_type { fn_type := ast.new_type(c.table.find_or_register_fn_type(method, false, true)) // if the expected type includes the receiver, don't hide it behind a closure @@ -1515,7 +1700,7 @@ fn (mut c Checker) const_decl(mut node ast.ConstDecl) { if node.fields.len == 0 { c.warn('const block must have at least 1 declaration', node.pos) } - for field in node.fields { + for mut field in node.fields { if checker.reserved_type_names_chk.matches(util.no_cur_mod(field.name, c.mod)) { c.error('invalid use of reserved type `${field.name}` as a const name', field.pos) } @@ -1527,13 +1712,28 @@ fn (mut c Checker) const_decl(mut node ast.ConstDecl) { } c.error('duplicate const `${field.name}`', name_pos) } + if field.expr is ast.CallExpr { + sym := c.table.sym(c.check_expr_option_or_result_call(field.expr, c.expr(mut field.expr))) + if sym.kind == .multi_return { + c.error('const declarations do not support multiple return values yet', + field.expr.pos()) + } + } + const_name := field.name.all_after_last('.') + if const_name == c.mod && const_name != 'main' { + name_pos := token.Pos{ + ...field.pos + len: util.no_cur_mod(field.name, c.mod).len + } + c.error('duplicate of a module name `${field.name}`', name_pos) + } c.const_names << field.name } for i, mut field in node.fields { c.const_deps << field.name prev_const_var := c.const_var c.const_var = unsafe { field } - mut typ := c.check_expr_opt_call(field.expr, c.expr(field.expr)) + mut typ := c.check_expr_option_or_result_call(field.expr, c.expr(mut field.expr)) if ct_value := c.eval_comptime_const_expr(field.expr, 0) { field.comptime_expr_value = ct_value if ct_value is u64 { @@ -1542,19 +1742,19 @@ fn (mut c Checker) const_decl(mut node ast.ConstDecl) { } node.fields[i].typ = ast.mktyp(typ) if mut field.expr is ast.IfExpr { - if field.expr.branches.len == 2 { - first_stmts := field.expr.branches[0].stmts - second_stmts := field.expr.branches[1].stmts - if first_stmts.len > 0 && first_stmts.last() is ast.ExprStmt - && (first_stmts.last() as ast.ExprStmt).typ != ast.void_type { + for branch in field.expr.branches { + if branch.stmts.len > 0 && branch.stmts.last() is ast.ExprStmt + && branch.stmts.last().typ != ast.void_type { field.expr.is_expr = true - field.expr.typ = (first_stmts.last() as ast.ExprStmt).typ - field.typ = field.expr.typ - } else if second_stmts.len > 0 && second_stmts.last() is ast.ExprStmt - && (second_stmts.last() as ast.ExprStmt).typ != ast.void_type { - field.expr.is_expr = true - field.expr.typ = (second_stmts.last() as ast.ExprStmt).typ + field.expr.typ = (branch.stmts.last() as ast.ExprStmt).typ field.typ = field.expr.typ + // update ConstField object's type in table + if mut obj := c.file.global_scope.find(field.name) { + if mut obj is ast.ConstField { + obj.typ = field.typ + } + } + break } } } @@ -1598,33 +1798,33 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { senum_type := c.table.type_to_str(node.typ) match node.typ { ast.i8_type { - signed, enum_imin, enum_imax = true, -128, 0x7F + signed, enum_imin, enum_imax = true, min_i8, max_i8 } ast.i16_type { - signed, enum_imin, enum_imax = true, -32_768, 0x7FFF + signed, enum_imin, enum_imax = true, min_i16, max_i16 } ast.int_type { - signed, enum_imin, enum_imax = true, -2_147_483_648, 0x7FFF_FFFF + signed, enum_imin, enum_imax = true, min_i32, max_i32 } ast.i64_type { - signed, enum_imin, enum_imax = true, i64(-9223372036854775807 - 1), i64(0x7FFF_FFFF_FFFF_FFFF) + signed, enum_imin, enum_imax = true, min_i64, max_i64 } // ast.u8_type { - signed, enum_umin, enum_umax = false, 0, 0xFF + signed, enum_umin, enum_umax = false, min_u8, max_u8 } ast.u16_type { - signed, enum_umin, enum_umax = false, 0, 0xFFFF + signed, enum_umin, enum_umax = false, min_u16, max_u16 } ast.u32_type { - signed, enum_umin, enum_umax = false, 0, 0xFFFF_FFFF + signed, enum_umin, enum_umax = false, min_u32, max_u32 } ast.u64_type { - signed, enum_umin, enum_umax = false, 0, 0xFFFF_FFFF_FFFF_FFFF + signed, enum_umin, enum_umax = false, min_u64, max_u64 } else { if senum_type == 'i32' { - signed, enum_imin, enum_imax = true, -2_147_483_648, 0x7FFF_FFFF + signed, enum_imin, enum_imax = true, min_i32, max_i32 } else { c.error('`${senum_type}` is not one of `i8`,`i16`,`i32`,`int`,`i64`,`u8`,`u16`,`u32`,`u64`', node.typ_pos) @@ -1648,47 +1848,32 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { if field.has_expr { match mut field.expr { ast.IntegerLiteral { - mut overflows := false - mut uval := u64(0) - mut ival := i64(0) - if signed { - val := field.expr.val.i64() - ival = val - if val < enum_imin || val >= enum_imax { - c.error('enum value `${field.expr.val}` overflows the enum type `${senum_type}`, values of which have to be in [${enum_imin}, ${enum_imax}]', - field.expr.pos) - overflows = true - } - } else { - val := field.expr.val.u64() - uval = val - if val >= enum_umax { - c.error('enum value `${field.expr.val}` overflows the enum type `${senum_type}`, values of which have to be in [${enum_umin}, ${enum_umax}]', - field.expr.pos) - overflows = true - } - } - if !overflows && !c.pref.translated && !c.file.is_translated - && !node.is_multi_allowed { - if (signed && ival in iseen) || (!signed && uval in useen) { - c.error('enum value `${field.expr.val}` already exists', field.expr.pos) - } - } - if signed { - iseen << ival - } else { - useen << uval - } - } - ast.PrefixExpr { - dump(field.expr) + c.check_enum_field_integer_literal(field.expr, signed, node.is_multi_allowed, + senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut + iseen, enum_imin, enum_imax) } ast.InfixExpr { // Handle `enum Foo { x = 1 + 2 }` c.infix_expr(mut field.expr) + mut t := transformer.new_transformer_with_table(c.table, c.pref) + folded_expr := t.infix_expr(mut field.expr) + + if folded_expr is ast.IntegerLiteral { + c.check_enum_field_integer_literal(folded_expr, signed, node.is_multi_allowed, + senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut + iseen, enum_imin, enum_imax) + } } ast.ParExpr { - c.expr(field.expr) + c.expr(mut field.expr.expr) + mut t := transformer.new_transformer_with_table(c.table, c.pref) + folded_expr := t.expr(mut field.expr.expr) + + if folded_expr is ast.IntegerLiteral { + c.check_enum_field_integer_literal(folded_expr, signed, node.is_multi_allowed, + senum_type, field.expr.pos, mut useen, enum_umin, enum_umax, mut + iseen, enum_imin, enum_imax) + } } ast.CastExpr { fe_type := c.cast_expr(mut field.expr) @@ -1707,6 +1892,13 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { if field.expr.language == .c { continue } + if field.expr.kind == .unresolved { + c.ident(mut field.expr) + } + if field.expr.kind == .constant && field.expr.obj.typ.is_int() { + // accepts int constants as enum value + continue + } } mut pos := field.expr.pos() if pos.pos == 0 { @@ -1749,6 +1941,53 @@ fn (mut c Checker) enum_decl(mut node ast.EnumDecl) { } } +fn (mut c Checker) check_enum_field_integer_literal(expr ast.IntegerLiteral, is_signed bool, is_multi_allowed bool, styp string, pos token.Pos, mut useen []u64, umin u64, umax u64, mut iseen []i64, imin i64, imax i64) { + mut overflows := false + mut uval := u64(0) + mut ival := i64(0) + + if is_signed { + val := expr.val.i64() + ival = val + if val < imin || val >= imax { + c.error('enum value `${expr.val}` overflows the enum type `${styp}`, values of which have to be in [${imin}, ${imax}]', + pos) + overflows = true + } + } else { + val := expr.val.u64() + uval = val + if val >= umax { + overflows = true + if val == umax { + is_bin := expr.val.starts_with('0b') + is_oct := expr.val.starts_with('0o') + is_hex := expr.val.starts_with('0x') + + if is_hex { + overflows = val.hex() != umax.hex() + } else if !is_bin && !is_oct && !is_hex { + overflows = expr.val.str() != umax.str() + } + } + if overflows { + c.error('enum value `${expr.val}` overflows the enum type `${styp}`, values of which have to be in [${umin}, ${umax}]', + pos) + } + } + } + if !overflows && !c.pref.translated && !c.file.is_translated && !is_multi_allowed { + if (is_signed && ival in iseen) || (!is_signed && uval in useen) { + c.error('enum value `${expr.val}` already exists', pos) + } + } + if is_signed { + iseen << ival + } else { + useen << uval + } +} + @[inline] fn (mut c Checker) check_loop_label(label string, pos token.Pos) { if label.len == 0 { @@ -1762,10 +2001,9 @@ fn (mut c Checker) check_loop_label(label string, pos token.Pos) { c.loop_label = label } -fn (mut c Checker) stmt(node_ ast.Stmt) { - mut node := unsafe { node_ } +fn (mut c Checker) stmt(mut node ast.Stmt) { $if trace_checker ? { - ntype := typeof(node).replace('v.ast.', '') + ntype := typeof(*node).replace('v.ast.', '') eprintln('checking: ${c.file.path:-30} | pos: ${node.pos.line_str():-39} | node: ${ntype} | ${node}') } c.expected_type = ast.void_type @@ -1777,23 +2015,24 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { } } ast.NodeError {} + ast.DebuggerStmt {} ast.AsmStmt { c.asm_stmt(mut node) } ast.AssertStmt { - c.assert_stmt(node) + c.assert_stmt(mut node) } ast.AssignStmt { c.assign_stmt(mut node) } ast.Block { - c.block(node) + c.block(mut node) } ast.BranchStmt { c.branch_stmt(node) } ast.ComptimeFor { - c.comptime_for(node) + c.comptime_for(mut node) } ast.ConstDecl { c.inside_const = true @@ -1811,9 +2050,10 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { for i, ident in node.defer_vars { mut id := ident if mut id.info is ast.IdentVar { - if id.comptime && id.name in constants.valid_comptime_not_user_defined { + if id.comptime && (id.tok_kind == .question + || id.name in ast.valid_comptime_not_user_defined) { node.defer_vars[i] = ast.Ident{ - scope: 0 + scope: unsafe { nil } name: '' } continue @@ -1827,14 +2067,14 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { } } c.inside_defer = true - c.stmts(node.stmts) + c.stmts(mut node.stmts) c.inside_defer = false } ast.EnumDecl { c.enum_decl(mut node) } ast.ExprStmt { - node.typ = c.expr(node.expr) + node.typ = c.expr(mut node.expr) c.expected_type = ast.void_type mut or_typ := ast.void_type match mut node.expr { @@ -1856,21 +2096,22 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { if mut node.expr is ast.InfixExpr { if node.expr.op == .left_shift { left_sym := c.table.final_sym(node.expr.left_type) - if left_sym.kind != .array { + if left_sym.kind != .array + && c.table.final_sym(c.unwrap_generic(node.expr.left_type)).kind != .array { c.error('unused expression', node.pos) } } } } - c.check_expr_opt_call(node.expr, or_typ) + c.check_expr_option_or_result_call(node.expr, or_typ) // TODO This should work, even if it's prolly useless .-. - // node.typ = c.check_expr_opt_call(node.expr, ast.void_type) + // node.typ = c.check_expr_option_or_result_call(node.expr, ast.void_type) } ast.FnDecl { c.fn_decl(mut node) } ast.ForCStmt { - c.for_c_stmt(node) + c.for_c_stmt(mut node) } ast.ForInStmt { c.for_in_stmt(mut node) @@ -1907,6 +2148,7 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { c.return_stmt(mut node) c.scope_returns = true } + ast.SemicolonStmt {} ast.SqlStmt { c.sql_stmt(mut node) } @@ -1919,17 +2161,17 @@ fn (mut c Checker) stmt(node_ ast.Stmt) { } } -fn (mut c Checker) assert_stmt(node ast.AssertStmt) { +fn (mut c Checker) assert_stmt(mut node ast.AssertStmt) { cur_exp_typ := c.expected_type c.expected_type = ast.bool_type - assert_type := c.check_expr_opt_call(node.expr, c.expr(node.expr)) + assert_type := c.check_expr_option_or_result_call(node.expr, c.expr(mut node.expr)) if assert_type != ast.bool_type_idx { atype_name := c.table.sym(assert_type).name c.error('assert can be used only with `bool` expressions, but found `${atype_name}` instead', node.pos) } if node.extra !is ast.EmptyExpr { - extra_type := c.expr(node.extra) + extra_type := c.expr(mut node.extra) if extra_type != ast.string_type { extra_type_name := c.table.sym(extra_type).name c.error('assert allows only a single string as its second argument, but found `${extra_type_name}` instead', @@ -1940,14 +2182,14 @@ fn (mut c Checker) assert_stmt(node ast.AssertStmt) { c.expected_type = cur_exp_typ } -fn (mut c Checker) block(node ast.Block) { +fn (mut c Checker) block(mut node ast.Block) { if node.is_unsafe { prev_unsafe := c.inside_unsafe c.inside_unsafe = true - c.stmts(node.stmts) + c.stmts(mut node.stmts) c.inside_unsafe = prev_unsafe } else { - c.stmts(node.stmts) + c.stmts(mut node.stmts) } } @@ -1956,7 +2198,7 @@ fn (mut c Checker) branch_stmt(node ast.BranchStmt) { c.error('`${node.kind.str()}` is not allowed in defer statements', node.pos) } if c.in_for_count == 0 { - if c.inside_comptime_for_field { + if c.comptime.inside_comptime_for { c.error('${node.kind.str()} is not allowed within a compile-time loop', node.pos) } else { c.error('${node.kind.str()} statement not within a loop', node.pos) @@ -1987,7 +2229,7 @@ fn (mut c Checker) global_decl(mut node ast.GlobalDecl) { c.error('the `main` function is the program entry point, cannot redefine it', field.pos) } - field.typ = c.expr(field.expr) + field.typ = c.expr(mut field.expr) mut v := c.file.global_scope.find_global(field.name) or { panic('internal compiler error - could not find global in scope') } @@ -2008,8 +2250,8 @@ fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { if c.pref.backend == .c && c.pref.ccompiler_type == .msvc { c.error('msvc compiler does not support inline assembly', stmt.pos) } - mut aliases := c.asm_ios(stmt.output, mut stmt.scope, true) - aliases2 := c.asm_ios(stmt.input, mut stmt.scope, false) + mut aliases := c.asm_ios(mut stmt.output, mut stmt.scope, true) + aliases2 := c.asm_ios(mut stmt.input, mut stmt.scope, false) aliases << aliases2 for mut template in stmt.templates { if template.is_directive { @@ -2062,7 +2304,7 @@ fn (mut c Checker) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt, aliases []string) { c.asm_arg(arg.base, stmt, aliases) c.asm_arg(arg.index, stmt, aliases) } - ast.BoolLiteral {} // all of these are guarented to be correct. + ast.BoolLiteral {} // all of these are guaranteed to be correct. ast.FloatLiteral {} ast.CharLiteral {} ast.IntegerLiteral {} @@ -2072,12 +2314,12 @@ fn (mut c Checker) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt, aliases []string) { } } -fn (mut c Checker) asm_ios(ios []ast.AsmIO, mut scope ast.Scope, output bool) []string { +fn (mut c Checker) asm_ios(mut ios []ast.AsmIO, mut scope ast.Scope, output bool) []string { mut aliases := []string{} - for io in ios { - typ := c.expr(io.expr) + for mut io in ios { + typ := c.expr(mut io.expr) if output { - c.fail_if_immutable(io.expr) + c.fail_if_immutable(mut io.expr) } if io.alias != '' { aliases << io.alias @@ -2105,13 +2347,13 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { node.ct_conds = c.ct_cond_stack.clone() } if c.pref.backend.is_js() || c.pref.backend == .golang { - // consider the the best way to handle the .go.vv files + // consider the best way to handle the .go.vv files if !c.file.path.ends_with('.js.v') && !c.file.path.ends_with('.go.v') && !c.file.path.ends_with('.go.vv') { c.error('hash statements are only allowed in backend specific files such "x.js.v" and "x.go.v"', node.pos) } - if c.mod == 'main' && c.pref.backend != .golang { + if c.pref.backend != .golang && c.mod == 'main' { c.error('hash statements are not allowed in the main module. Place them in a separate module.', node.pos) } @@ -2247,8 +2489,10 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if node.kind == 'define' { if !c.is_builtin_mod && !c.file.path.ends_with('.c.v') && !c.file.path.contains('vlib') { - c.error("#define can only be used in vlib (V's standard library) and *.c.v files", - node.pos) + if !c.pref.is_bare { + c.error("#define can only be used in vlib (V's standard library) and *.c.v files", + node.pos) + } } } else { c.error('expected `#define`, `#flag`, `#include`, `#insert` or `#pkgconfig` not ${node.val}', @@ -2295,10 +2539,10 @@ fn (mut c Checker) import_stmt(node ast.Import) { } // stmts should be used for processing normal statement lists (fn bodies, for loop bodies etc). -fn (mut c Checker) stmts(stmts []ast.Stmt) { +fn (mut c Checker) stmts(mut stmts []ast.Stmt) { old_stmt_level := c.stmt_level c.stmt_level = 0 - c.stmts_ending_with_expression(stmts) + c.stmts_ending_with_expression(mut stmts) c.stmt_level = old_stmt_level } @@ -2307,7 +2551,7 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) { // `x := opt() or { stmt1 stmt2 ExprStmt }`, // `x := if cond { stmt1 stmt2 ExprStmt } else { stmt2 stmt3 ExprStmt }`, // `x := match expr { Type1 { stmt1 stmt2 ExprStmt } else { stmt2 stmt3 ExprStmt }`. -fn (mut c Checker) stmts_ending_with_expression(stmts []ast.Stmt) { +fn (mut c Checker) stmts_ending_with_expression(mut stmts []ast.Stmt) { if stmts.len == 0 { c.scope_returns = false return @@ -2321,14 +2565,14 @@ fn (mut c Checker) stmts_ending_with_expression(stmts []ast.Stmt) { line_nr: -1 } c.stmt_level++ - for i, stmt in stmts { + for i, mut stmt in stmts { c.is_last_stmt = i == stmts.len - 1 if c.scope_returns { if unreachable.line_nr == -1 { unreachable = stmt.pos } } - c.stmt(stmt) + c.stmt(mut stmt) if stmt is ast.GotoLabel { unreachable = token.Pos{ line_nr: -1 @@ -2348,24 +2592,30 @@ fn (mut c Checker) stmts_ending_with_expression(stmts []ast.Stmt) { } fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { - if typ.has_flag(.generic) && c.table.cur_fn != unsafe { nil } { - if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, - c.table.cur_concrete_types) - { - return t_typ + if typ.has_flag(.generic) { + if c.inside_generic_struct_init { + generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) + if t_typ := c.table.resolve_generic_to_concrete(typ, generic_names, c.cur_struct_concrete_types) { + return t_typ + } + } + if c.table.cur_fn != unsafe { nil } { + if t_typ := c.table.resolve_generic_to_concrete(typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + { + return t_typ + } } } return typ } -// TODO node must be mut -pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { +pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { c.expr_level++ defer { c.expr_level-- } - mut node := unsafe { node_ } if c.expr_level > checker.expr_level_cutoff_limit { c.error('checker: too many expr levels: ${c.expr_level} ', node.pos()) return ast.void_type @@ -2389,7 +2639,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { return c.anon_fn(mut node) } ast.ArrayDecompose { - typ := c.expr(node.expr) + typ := c.expr(mut node.expr) type_sym := c.table.sym(typ) if type_sym.kind == .array_fixed { c.error('direct decomposition of fixed array is not allowed, convert the fixed array to normal array via ${node.expr}[..]', @@ -2409,18 +2659,22 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { return c.array_init(mut node) } ast.AsCast { - node.expr_type = c.expr(node.expr) + node.expr_type = c.expr(mut node.expr) expr_type_sym := c.table.sym(node.expr_type) - type_sym := c.table.sym(node.typ) + type_sym := c.table.sym(c.unwrap_generic(node.typ)) if expr_type_sym.kind == .sum_type { - c.ensure_type_exists(node.typ, node.pos) or {} - if !c.table.sumtype_has_variant(node.expr_type, node.typ, true) { + c.ensure_type_exists(node.typ, node.pos) + if !c.table.sumtype_has_variant(c.unwrap_generic(node.expr_type), c.unwrap_generic(node.typ), + true) { addr := '&'.repeat(node.typ.nr_muls()) c.error('cannot cast `${expr_type_sym.name}` to `${addr}${type_sym.name}`', node.pos) } - } else if expr_type_sym.kind == .interface_ && type_sym.kind == .interface_ { - c.ensure_type_exists(node.typ, node.pos) or {} + } else if expr_type_sym.kind == .interface_ { + c.ensure_type_exists(node.typ, node.pos) + if type_sym.kind != .interface_ { + c.type_implements(node.typ, node.expr_type, node.pos) + } } else if node.expr_type.clear_flag(.option) != node.typ.clear_flag(.option) { mut s := 'cannot cast non-sum type `${expr_type_sym.name}` using `as`' if type_sym.kind == .sum_type { @@ -2433,7 +2687,8 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { ast.Assoc { v := node.scope.find_var(node.var_name) or { panic(err) } for i, _ in node.fields { - c.expr(node.exprs[i]) + mut expr := node.exprs[i] + c.expr(mut expr) } node.typ = v.typ return v.typ @@ -2448,21 +2703,13 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { mut ret_type := c.call_expr(mut node) if ret_type != 0 && c.table.sym(ret_type).kind == .alias { unaliased_type := c.table.unaliased_type(ret_type) - if unaliased_type.has_flag(.option) || unaliased_type.has_flag(.result) { + if unaliased_type.has_option_or_result() { ret_type = unaliased_type } } - if !ret_type.has_flag(.option) && !ret_type.has_flag(.result) { - if node.or_block.kind == .block { - c.error('unexpected `or` block, the function `${node.name}` does not return an Option or a Result', - node.or_block.pos) - } else if node.or_block.kind == .propagate_option { - c.error('unexpected `?`, the function `${node.name}` does not return an Option or a Result', - node.or_block.pos) - } else if node.or_block.kind == .propagate_result { - c.error('unexpected `!`, the function `${node.name}` does not return an Option or a Result', - node.or_block.pos) - } + if !ret_type.has_option_or_result() { + c.expr_or_block_err(node.or_block.kind, node.name, node.or_block.pos, + false) } if node.or_block.kind != .absent { if ret_type.has_flag(.option) { @@ -2497,16 +2744,16 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.DumpExpr { c.expected_type = ast.string_type - node.expr_type = c.expr(node.expr) + node.expr_type = c.expr(mut node.expr) - if c.inside_comptime_for_field && node.expr is ast.Ident { - if c.is_comptime_var(node.expr) { - node.expr_type = c.get_comptime_var_type(node.expr as ast.Ident) - } else if (node.expr as ast.Ident).name in c.comptime_fields_type { - node.expr_type = c.comptime_fields_type[(node.expr as ast.Ident).name] + if c.comptime.inside_comptime_for && node.expr is ast.Ident { + if c.comptime.is_comptime_var(node.expr) { + node.expr_type = c.comptime.get_comptime_var_type(node.expr as ast.Ident) + } else if (node.expr as ast.Ident).name in c.comptime.type_map { + node.expr_type = c.comptime.type_map[(node.expr as ast.Ident).name] } } - c.check_expr_opt_call(node.expr, node.expr_type) + c.check_expr_option_or_result_call(node.expr, node.expr_type) etidx := node.expr_type.idx() if etidx == ast.void_type_idx { c.error('dump expression can not be void', node.expr.pos()) @@ -2519,6 +2766,14 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { unwrapped_expr_type := c.unwrap_generic(node.expr_type) tsym := c.table.sym(unwrapped_expr_type) + if tsym.kind == .array_fixed { + info := tsym.info as ast.ArrayFixed + if !info.is_fn_ret { + // for dumping fixed array we must register the fixed array struct to return from function + c.table.find_or_register_array_fixed(info.elem_type, info.size, info.size_expr, + true) + } + } type_cname := if node.expr_type.has_flag(.option) { '_option_${tsym.cname}' } else { @@ -2537,6 +2792,9 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { ast.GoExpr { return c.go_expr(mut node) } + ast.SpawnExpr { + return c.spawn_expr(mut node) + } ast.Ident { return c.ident(mut node) } @@ -2546,7 +2804,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { ast.IfGuardExpr { old_inside_if_guard := c.inside_if_guard c.inside_if_guard = true - node.expr_type = c.expr(node.expr) + node.expr_type = c.expr(mut node.expr) c.inside_if_guard = old_inside_if_guard if !node.expr_type.has_flag(.option) && !node.expr_type.has_flag(.result) { mut no_opt_or_res := true @@ -2580,6 +2838,13 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { ast.IntegerLiteral { return c.int_lit(mut node) } + ast.LambdaExpr { + c.inside_lambda = true + defer { + c.inside_lambda = false + } + return c.lambda_expr(mut node, c.expected_type) + } ast.LockExpr { return c.lock_expr(mut node) } @@ -2615,12 +2880,12 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { if node.expr is ast.ParExpr { c.warn('redundant parentheses are used', node.pos) } - return c.expr(node.expr) + return c.expr(mut node.expr) } ast.RangeExpr { // branch range expression of `match x { a...b {} }`, or: `a#[x..y]`: - ltyp := c.expr(node.low) - htyp := c.expr(node.high) + ltyp := c.expr(mut node.low) + htyp := c.expr(mut node.high) if !c.check_types(ltyp, htyp) { lstype := c.table.type_to_str(ltyp) hstype := c.table.type_to_str(htyp) @@ -2641,18 +2906,9 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { if c.table.sym(ret_type).kind == .chan { return ret_type } - if !ret_type.has_flag(.option) && !ret_type.has_flag(.result) { - if node.or_block.kind == .block { - c.error('unexpected `or` block, the field `${node.field_name}` is neither an Option, nor a Result', - node.or_block.pos) - } else if node.or_block.kind == .propagate_option { - c.error('unexpected `?`, the field `${node.field_name}` is neither an Option, nor a Result', - node.or_block.pos) - } else if node.or_block.kind == .propagate_result { - c.error('unexpected `!`, the field `${node.field_name}` is neither an Option, nor a Result', - node.or_block.pos) - } + c.expr_or_block_err(node.or_block.kind, node.field_name, node.or_block.pos, + true) } if node.or_block.kind != .absent { if ret_type.has_flag(.option) { @@ -2666,7 +2922,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.SizeOf { if !node.is_type { - node.typ = c.expr(node.expr) + node.typ = c.expr(mut node.expr) } sym := c.table.final_sym(node.typ) if sym.kind == .placeholder && sym.language != .c { @@ -2679,7 +2935,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.IsRefType { if !node.is_type { - node.typ = c.expr(node.expr) + node.typ = c.expr(mut node.expr) } // c.deprecate_old_isreftype_and_sizeof_of_a_guessed_type(node.guessed_type, // node.typ, node.pos, 'isreftype') @@ -2703,7 +2959,8 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.StructInit { if node.unresolved { - return c.expr(c.table.resolve_init(node, c.unwrap_generic(node.typ))) + mut expr_ := c.table.resolve_init(node, c.unwrap_generic(node.typ)) + return c.expr(mut expr_) } mut inited_fields := []string{} return c.struct_init(mut node, false, mut inited_fields) @@ -2718,7 +2975,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { } ast.TypeOf { if !node.is_type { - node.typ = c.expr(node.expr) + node.typ = c.expr(mut node.expr) } return ast.string_type } @@ -2726,7 +2983,7 @@ pub fn (mut c Checker) expr(node_ ast.Expr) ast.Type { return c.unsafe_expr(mut node) } ast.Likely { - ltype := c.expr(node.expr) + ltype := c.expr(mut node.expr) if !c.check_types(ltype, ast.bool_type) { ltype_sym := c.table.sym(ltype) lname := if node.is_likely { '_likely_' } else { '_unlikely_' } @@ -2760,11 +3017,10 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { // Given: `Outside( Inside(xyz) )`, // node.expr_type: `Inside` // node.typ: `Outside` - node.expr_type = c.expr(node.expr) // type to be casted + node.expr_type = c.expr(mut node.expr) // type to be casted - if node.expr is ast.ComptimeSelector { - node.expr_type = c.get_comptime_selector_type(node.expr as ast.ComptimeSelector, - node.expr_type) + if mut node.expr is ast.ComptimeSelector { + node.expr_type = c.comptime.get_comptime_selector_type(node.expr, node.expr_type) } mut from_type := c.unwrap_generic(node.expr_type) @@ -2774,6 +3030,12 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { mut to_type := c.unwrap_generic(node.typ) mut to_sym := c.table.sym(to_type) // type to be used as cast mut final_to_sym := c.table.final_sym(to_type) + final_to_type := if mut to_sym.info is ast.Alias { + to_sym.info.parent_type + } else { + to_type + } + final_to_is_ptr := to_type.is_ptr() || final_to_type.is_ptr() if to_type.has_flag(.result) { c.error('casting to Result type is forbidden', node.pos) @@ -2791,7 +3053,13 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } if to_sym.language != .c { - c.ensure_type_exists(to_type, node.pos) or {} + c.ensure_type_exists(to_type, node.pos) + + if to_sym.info is ast.Alias && to_sym.info.parent_type.has_flag(.option) + && !to_type.has_flag(.option) { + c.error('alias to Option type requires to be used as Option type (?${to_sym.name}(...))', + node.pos) + } } if from_sym.kind == .u8 && from_type.is_ptr() && to_sym.kind == .string && !to_type.is_ptr() { c.error('to convert a C string buffer pointer to a V string, use x.vstring() instead of string(x)', @@ -2803,6 +3071,11 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { if to_type.has_flag(.option) && from_type == ast.none_type { // allow conversion from none to every option type } else if to_sym.kind == .sum_type { + to_sym_info := to_sym.info as ast.SumType + if to_sym_info.generic_types.len > 0 && to_sym_info.concrete_types.len == 0 { + c.error('generic sumtype `${to_sym.name}` must specify type parameter, e.g. ${to_sym.name}[int]', + node.pos) + } if from_type in [ast.int_literal_type, ast.float_literal_type] { xx := if from_type == ast.int_literal_type { ast.int_type } else { ast.f64_type } node.expr_type = c.promote_num(node.expr_type, xx) @@ -2814,25 +3087,26 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { tt := c.table.type_to_str(to_type) c.error('cannot cast `${ft}` to `${tt}`', node.pos) } - } else if mut to_sym.info is ast.Alias && !(final_to_sym.kind == .struct_ && to_type.is_ptr()) { - if !c.check_types(from_type, to_sym.info.parent_type) && !(final_to_sym.is_int() - && final_from_sym.kind in [.enum_, .bool, .i8, .u8, .char]) { + } else if mut to_sym.info is ast.Alias && !(final_to_sym.kind == .struct_ && final_to_is_ptr) { + if (!c.check_types(from_type, to_sym.info.parent_type) && !(final_to_sym.is_int() + && final_from_sym.kind in [.enum_, .bool, .i8, .u8, .char])) + || (final_to_sym.kind == .struct_ + && from_type.idx() in [ast.voidptr_type_idx, ast.nil_type_idx]) { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast `${ft}` to `${tt}` (alias to `${final_to_sym.name}`)', node.pos) } - } else if to_sym.kind == .struct_ && !to_type.is_ptr() - && !(to_sym.info as ast.Struct).is_typedef { - // For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard - if from_sym.kind == .struct_ && !from_type.is_ptr() { + } else if to_sym.kind == .struct_ && mut to_sym.info is ast.Struct + && (!to_sym.info.is_typedef || from_type.idx() in [ast.voidptr_type_idx, ast.nil_type_idx]) + && !final_to_is_ptr { + // For now we ignore C typedef because of `C.Window(C.None)` in vlib/clipboard (except for `from_type` is voidptr/nil) + if from_sym.kind == .struct_ && from_sym.info is ast.Struct && !from_type.is_ptr() { if !to_type.has_flag(.option) { c.warn('casting to struct is deprecated, use e.g. `Struct{...expr}` instead', node.pos) } - from_type_info := from_sym.info as ast.Struct - to_type_info := to_sym.info as ast.Struct - if !c.check_struct_signature(from_type_info, to_type_info) { + if !c.check_struct_signature(from_sym.info, to_sym.info) { c.error('cannot convert struct `${from_sym.name}` to struct `${to_sym.name}`', node.pos) } @@ -2840,9 +3114,9 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { ft := c.table.type_to_str(from_type) c.error('cannot cast `${ft}` to struct', node.pos) } - } else if to_sym.kind == .struct_ && to_type.is_ptr() { - if from_sym.kind == .alias { - from_type = (from_sym.info as ast.Alias).parent_type.derive_add_muls(from_type) + } else if to_sym.kind == .struct_ && final_to_is_ptr { + if from_sym.info is ast.Alias { + from_type = from_sym.info.parent_type.derive_add_muls(from_type) } if mut node.expr is ast.IntegerLiteral { if node.expr.val.int() == 0 && !c.pref.translated && !c.file.is_translated { @@ -2868,23 +3142,23 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { else {} } } - if from_type == ast.voidptr_type_idx && !c.inside_unsafe { - // TODO make this an error - c.warn('cannot cast voidptr to a struct outside `unsafe`', node.pos) + if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated + && !c.file.is_translated { + c.error('cannot cast voidptr to a struct outside `unsafe`', node.pos) } - if !from_type.is_int() && final_from_sym.kind != .enum_ && !from_type.is_pointer() - && !from_type.is_ptr() { + if !from_type.is_int() && final_from_sym.kind != .enum_ + && !from_type.is_any_kind_of_pointer() { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast `${ft}` to `${tt}`', node.pos) } - } else if to_sym.kind == .interface_ { + } else if !from_type.has_option_or_result() && mut to_sym.info is ast.Interface { if c.type_implements(from_type, to_type, node.pos) { - if !from_type.is_ptr() && !from_type.is_pointer() && from_sym.kind != .interface_ + if !from_type.is_any_kind_of_pointer() && from_sym.kind != .interface_ && !c.inside_unsafe { c.mark_as_referenced(mut &node.expr, true) } - if (to_sym.info as ast.Interface).is_generic { + if to_sym.info.is_generic { inferred_type := c.resolve_generic_interface(from_type, to_type, node.pos) if inferred_type != 0 { to_type = inferred_type @@ -2904,14 +3178,14 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } else if from_type == ast.none_type && !to_type.has_flag(.option) && !to_type.has_flag(.result) { type_name := c.table.type_to_str(to_type) c.error('cannot cast `none` to `${type_name}`', node.pos) - } else if from_sym.kind == .struct_ && !from_type.is_ptr() { - if (to_type.is_ptr() || to_sym.kind !in [.sum_type, .interface_]) && !c.is_builtin_mod { + } else if !from_type.has_option_or_result() && from_sym.kind == .struct_ && !from_type.is_ptr() { + if (final_to_is_ptr || to_sym.kind !in [.sum_type, .interface_]) && !c.is_builtin_mod { from_type_name := c.table.type_to_str(from_type) type_name := c.table.type_to_str(to_type) c.error('cannot cast struct `${from_type_name}` to `${type_name}`', node.pos) } - } else if to_sym.kind == .u8 && !final_from_sym.is_number() && !final_from_sym.is_pointer() - && !from_type.is_ptr() && final_from_sym.kind !in [.char, .enum_, .bool] { + } else if to_sym.kind == .u8 && !final_from_sym.is_number() + && !from_type.is_any_kind_of_pointer() && final_from_sym.kind !in [.char, .enum_, .bool] { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast type `${ft}` to `${tt}`', node.pos) @@ -2932,14 +3206,16 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { tt := c.table.type_to_str(to_type) c.warn('casting `${ft}` to `${tt}` is only allowed in `unsafe` code', node.pos) } else if from_sym.kind == .array_fixed && !from_type.is_ptr() { - c.warn('cannot cast a fixed array (use e.g. `&arr[0]` instead)', node.pos) + if !c.pref.translated && !c.file.is_translated { + c.warn('cannot cast a fixed array (use e.g. `&arr[0]` instead)', node.pos) + } } else if final_from_sym.kind == .string && final_to_sym.is_number() && final_to_sym.kind != .rune { snexpr := node.expr.str() tt := c.table.type_to_str(to_type) c.error('cannot cast string to `${tt}`, use `${snexpr}.${final_to_sym.name}()` instead.', node.pos) - } else if final_from_sym.kind == .string && to_type.is_ptr() && to_sym.kind != .string { + } else if final_from_sym.kind == .string && final_to_is_ptr && to_sym.kind != .string { snexpr := node.expr.str() tt := c.table.type_to_str(to_type) c.error('cannot cast string to `${tt}`, use `${snexpr}.str` instead.', node.pos) @@ -2947,6 +3223,13 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { snexpr := node.expr.str() tt := c.table.type_to_str(to_type) c.error('cannot cast string to `${tt}`, use `${snexpr}[index]` instead.', node.pos) + } else if final_from_sym.kind == .string && to_type.is_voidptr() + && !node.expr_type.has_flag(.generic) && !from_type.is_ptr() { + c.error('cannot cast string to `voidptr`, use voidptr(s.str) instead', node.pos) + } else if final_from_sym.kind == .string && to_type.is_pointer() && !c.inside_unsafe { + tt := c.table.type_to_str(to_type) + c.error('cannot cast string to `${tt}` outside `unsafe`, use ${tt}(s.str) instead', + node.pos) } else if final_from_sym.kind == .array && !from_type.is_ptr() && to_type != ast.string_type && !(to_type.has_flag(.option) && from_type.idx() == to_type.idx()) { ft := c.table.type_to_str(from_type) @@ -2975,6 +3258,10 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { c.error('casting a function value from one function signature, to another function signature, should be done inside `unsafe{}` blocks', node.pos) } + if to_type.is_ptr() && to_sym.kind == .alias && from_sym.kind == .map { + c.error('cannot cast to alias pointer `${c.table.type_to_str(to_type)}` because `${c.table.type_to_str(from_type)}` is a value', + node.pos) + } if to_type == ast.string_type { if from_type in [ast.u8_type, ast.bool_type] { @@ -2982,7 +3269,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { ft := c.table.type_to_str(from_type) c.error('cannot cast type `${ft}` to string, use `${snexpr}.str()` instead.', node.pos) - } else if from_type.is_real_pointer() { + } else if from_type.is_any_kind_of_pointer() { snexpr := node.expr.str() ft := c.table.type_to_str(from_type) c.error('cannot cast pointer type `${ft}` to string, use `&u8(${snexpr}).vstring()` or `cstring_to_vstring(${snexpr})` instead.', @@ -3026,6 +3313,33 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } c.error(error_msg, node.pos) } + } else if to_type.is_int() && mut node.expr is ast.IntegerLiteral { + tt := c.table.type_to_str(to_type) + tsize, _ := c.table.type_size(to_type.idx()) + bit_size := tsize * 8 + value_string := match node.expr.val[0] { + `-`, `+` { + node.expr.val[1..] + } + else { + node.expr.val + } + } + _, e := strconv.common_parse_uint2(value_string, 0, bit_size) + match e { + 0 {} + -3 { + c.error('value `${node.expr.val}` overflows `${tt}`', node.pos) + } + else { + c.error('cannot cast value `${node.expr.val}` to `${tt}`', node.pos) + } + } + } else if to_type.is_float() && mut node.expr is ast.FloatLiteral { + tt := c.table.type_to_str(to_type) + strconv.atof64(node.expr.val) or { + c.error('cannot cast value `${node.expr.val}` to `${tt}`', node.pos) + } } if from_sym.language == .v && !from_type.is_ptr() && final_from_sym.kind in [.sum_type, .interface_] @@ -3038,7 +3352,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } if node.has_arg { - c.expr(node.arg) + c.expr(mut node.arg) } // checks on int literal to enum cast if the value represents a value on the enum @@ -3079,6 +3393,10 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } } } + if node.expr_type == ast.string_type_idx { + c.add_error_detail('use ${c.table.type_to_str(node.typ)}.from_string(${node.expr}) instead') + c.error('cannot cast `string` to `enum`', node.pos) + } } node.typname = c.table.sym(node.typ).name return node.typ @@ -3111,7 +3429,7 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { node.val = c.table.cur_fn.mod } .struct_name { - if c.table.cur_fn.is_method { + if c.table.cur_fn.is_method || c.table.cur_fn.is_static_type_method { node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') } else { node.val = '' @@ -3132,9 +3450,27 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { .column_nr { node.val = (node.pos.col + 1).str() } + .location { + mut mname := 'unknown' + if c.table.cur_fn != unsafe { nil } { + if c.table.cur_fn.is_method { + mname = c.table.type_to_str(c.table.cur_fn.receiver.typ) + '{}.' + + c.table.cur_fn.name.all_after_last('.') + } else { + mname = c.table.cur_fn.name + } + if c.table.cur_fn.is_static_type_method { + mname = mname.replace('__static__', '.') + ' (static)' + } + } + node.val = c.file.path + ':' + (node.pos.line_nr + 1).str() + ', ${mname}' + } .vhash { node.val = version.vhash() } + .v_current_hash { + node.val = c.v_current_commit_hash + } .vmod_file { // cache the vmod content, do not read it many times if c.vmod_file_content.len == 0 { @@ -3168,7 +3504,16 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { return ast.string_type } +struct ACFieldMethod { + name string + typ string +} + fn (mut c Checker) ident(mut node ast.Ident) ast.Type { + if c.pref.linfo.is_running { + // Mini LS hack (v -line-info "a.v:16") + c.ident_autocomplete(node) + } // TODO: move this if c.const_deps.len > 0 { mut name := node.name @@ -3206,8 +3551,8 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { // second use if node.kind in [.constant, .global, .variable] { info := node.info as ast.IdentVar - typ := if c.is_comptime_var(node) { - ctype := c.get_comptime_var_type(node) + typ := if c.comptime.is_comptime_var(node) { + ctype := c.comptime.get_comptime_var_type(node) if ctype != ast.void_type { ctype } else { @@ -3225,9 +3570,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { c.error('cannot use `or {}` block on non-option variable', node.pos) } } - unwrapped_typ := typ.clear_flags(.option, .result) + unwrapped_typ := typ.clear_option_and_result() c.expected_or_type = unwrapped_typ - c.stmts_ending_with_expression(node.or_expr.stmts) + c.stmts_ending_with_expression(mut node.or_expr.stmts) c.check_or_expr(node.or_expr, typ, c.expected_or_type, node) return unwrapped_typ } @@ -3237,7 +3582,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { if func := c.table.find_fn(node.name) { if func.generic_names.len > 0 { concrete_types := node.concrete_types.map(c.unwrap_generic(it)) - c.table.register_fn_concrete_types(func.fkey(), concrete_types) + if concrete_types.all(!it.has_flag(.generic)) { + c.table.register_fn_concrete_types(func.fkey(), concrete_types) + } } } return info.typ @@ -3297,17 +3644,19 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { } } } else { - typ = obj.expr.expr_type.clear_flags(.option, .result) + typ = obj.expr.expr_type.clear_option_and_result() } } else if obj.expr is ast.EmptyExpr { c.error('invalid variable `${node.name}`', node.pos) typ = ast.void_type } else { - typ = c.expr(obj.expr) + typ = c.expr(mut obj.expr) } } - is_option := typ.has_flag(.option) || typ.has_flag(.result) - || node.or_expr.kind != .absent + if c.inside_interface_deref && c.table.is_interface_var(obj) { + typ = typ.deref() + } + is_option := typ.has_option_or_result() || node.or_expr.kind != .absent node.kind = .variable node.info = ast.IdentVar{ typ: typ @@ -3330,9 +3679,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { node.pos) } } - unwrapped_typ := typ.clear_flags(.option, .result) + unwrapped_typ := typ.clear_option_and_result() c.expected_or_type = unwrapped_typ - c.stmts_ending_with_expression(node.or_expr.stmts) + c.stmts_ending_with_expression(mut node.or_expr.stmts) c.check_or_expr(node.or_expr, typ, c.expected_or_type, node) return unwrapped_typ } @@ -3369,13 +3718,13 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { old_c_mod := c.mod c.mod = obj.mod c.inside_const = true - typ = c.expr(obj.expr) + typ = c.expr(mut obj.expr) c.inside_const = false c.mod = old_c_mod if mut obj.expr is ast.CallExpr { if obj.expr.or_block.kind != .absent { - typ = typ.clear_flags(.option, .result) + typ = typ.clear_option_and_result() } } } @@ -3386,6 +3735,17 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { } obj.typ = typ node.obj = obj + + if obj.attrs.contains('deprecated') && obj.mod != c.mod { + c.deprecate('const', obj.name, obj.attrs, node.pos) + } + + if node.or_expr.kind != .absent { + unwrapped_typ := typ.clear_option_and_result() + c.expected_or_type = unwrapped_typ + c.stmts_ending_with_expression(mut node.or_expr.stmts) + c.check_or_expr(node.or_expr, typ, c.expected_or_type, node) + } return typ } else {} @@ -3401,7 +3761,9 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { concrete_types) { fn_type = typ_ - c.table.register_fn_concrete_types(func.fkey(), concrete_types) + if concrete_types.all(!it.has_flag(.generic)) { + c.table.register_fn_concrete_types(func.fkey(), concrete_types) + } } } node.name = name @@ -3429,6 +3791,12 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { saved_mod := node.mod node.mod = 'builtin' builtin_type := c.ident(mut node) + if node.obj is ast.ConstField { + field := node.obj as ast.ConstField + if field.attrs.contains('deprecated') && field.mod != c.mod { + c.deprecate('const', field.name, field.attrs, node.pos) + } + } if builtin_type != ast.void_type { return builtin_type } @@ -3462,8 +3830,13 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { found_var := c.fn_scope.find_var(node.name) if found_var != none { - c.error('`${node.name}` must be added to the capture list for the closure to be used inside', - node.pos) + if c.inside_lambda { + // Lambdas don't support capturing variables yet, so that's the only hint. + c.error('undefined variable `${node.name}`', node.pos) + } else { + c.error('`${node.name}` must be added to the capture list for the closure to be used inside', + node.pos) + } return ast.void_type } } @@ -3481,8 +3854,8 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type { mut mr_types := []ast.Type{} - for expr in node.vals { - mr_types << c.expr(expr) + for mut expr in node.vals { + mr_types << c.expr(mut expr) } if node.vals.len == 1 { typ := mr_types[0] @@ -3503,14 +3876,13 @@ fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type { } // smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope -fn (mut c Checker) smartcast(expr_ ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope) { +fn (mut c Checker) smartcast(mut expr ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope, is_comptime bool) { sym := c.table.sym(cur_type) to_type := if sym.kind == .interface_ && c.table.sym(to_type_).kind != .interface_ { to_type_.ref() } else { to_type_ } - mut expr := unsafe { expr_ } match mut expr { ast.SelectorExpr { mut is_mut := false @@ -3552,6 +3924,8 @@ fn (mut c Checker) smartcast(expr_ ast.Expr, cur_type ast.Type, to_type_ ast.Typ mut smartcasts := []ast.Type{} mut is_already_casted := false mut orig_type := 0 + mut is_inherited := false + mut ct_type_var := ast.ComptimeVarKind.no_comptime if mut expr.obj is ast.Var { is_mut = expr.obj.is_mut smartcasts << expr.obj.smartcasts @@ -3559,18 +3933,32 @@ fn (mut c Checker) smartcast(expr_ ast.Expr, cur_type ast.Type, to_type_ ast.Typ if orig_type == 0 { orig_type = expr.obj.typ } + is_inherited = expr.obj.is_inherited + ct_type_var = if is_comptime { + .smartcast + } else { + .no_comptime + } } // smartcast either if the value is immutable or if the mut argument is explicitly given if (!is_mut || expr.is_mut) && !is_already_casted { smartcasts << to_type + if var := scope.find_var(expr.name) { + if is_comptime && var.ct_type_var == .smartcast { + scope.update_smartcasts(expr.name, to_type) + return + } + } scope.register(ast.Var{ name: expr.name typ: cur_type pos: expr.pos is_used: true is_mut: expr.is_mut + is_inherited: is_inherited smartcasts: smartcasts orig_type: orig_type + ct_type_var: ct_type_var }) } else if is_mut && !expr.is_mut { c.smartcast_mut_pos = expr.pos @@ -3585,9 +3973,9 @@ fn (mut c Checker) smartcast(expr_ ast.Expr, cur_type ast.Type, to_type_ ast.Typ fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type { node.is_expr = c.expected_type != ast.void_type node.expected_type = c.expected_type - for branch in node.branches { - c.stmt(branch.stmt) - match branch.stmt { + for mut branch in node.branches { + c.stmt(mut branch.stmt) + match mut branch.stmt { ast.ExprStmt { if branch.is_timeout { if !branch.stmt.typ.is_int() { @@ -3596,7 +3984,7 @@ fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type { branch.stmt.pos) } } else { - if branch.stmt.expr is ast.InfixExpr { + if mut branch.stmt.expr is ast.InfixExpr { if branch.stmt.expr.left !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] { c.error('channel in `select` key must be predefined', branch.stmt.expr.left.pos()) } @@ -3638,7 +4026,7 @@ fn (mut c Checker) select_expr(mut node ast.SelectExpr) ast.Type { } } } - c.stmts(branch.stmts) + c.stmts(mut branch.stmts) } return ast.bool_type } @@ -3649,7 +4037,8 @@ fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type { c.error('nested `lock`/`rlock` not allowed', node.pos) } for i in 0 .. node.lockeds.len { - e_typ := c.expr(node.lockeds[i]) + mut expr_ := node.lockeds[i] + e_typ := c.expr(mut expr_) id_name := node.lockeds[i].str() if !e_typ.has_flag(.shared_f) { obj_type := if node.lockeds[i] is ast.Ident { 'variable' } else { 'struct element' } @@ -3667,14 +4056,14 @@ fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type { c.locked_names << id_name } } - c.stmts(node.stmts) + c.stmts(mut node.stmts) // handle `x := rlock a { a.getval() }` mut ret_type := ast.void_type if node.stmts.len > 0 { - last_stmt := node.stmts.last() - if last_stmt is ast.ExprStmt { + mut last_stmt := node.stmts.last() + if mut last_stmt is ast.ExprStmt { c.expected_type = expected_type - ret_type = c.expr(last_stmt.expr) + ret_type = c.expr(mut last_stmt.expr) } } c.rlocked_names = [] @@ -3689,7 +4078,7 @@ fn (mut c Checker) lock_expr(mut node ast.LockExpr) ast.Type { fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) ast.Type { prev_unsafe := c.inside_unsafe c.inside_unsafe = true - t := c.expr(node.expr) + t := c.expr(mut node.expr) c.inside_unsafe = prev_unsafe return t } @@ -3747,12 +4136,6 @@ fn (c &Checker) has_return(stmts []ast.Stmt) ?bool { return none } -@[inline] -pub fn (mut c Checker) is_comptime_var(node ast.Expr) bool { - return node is ast.Ident - && (node as ast.Ident).info is ast.IdentVar && (node as ast.Ident).kind == .variable && ((node as ast.Ident).obj as ast.Var).ct_type_var != .no_comptime -} - fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) { match mut node { ast.Ident { @@ -3826,7 +4209,7 @@ fn (mut c Checker) get_base_name(node &ast.Expr) string { fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { old_inside_ref_lit := c.inside_ref_lit c.inside_ref_lit = c.inside_ref_lit || node.op == .amp - right_type := c.expr(node.right) + right_type := c.expr(mut node.right) c.inside_ref_lit = old_inside_ref_lit node.right_type = right_type if node.op == .amp { @@ -3838,6 +4221,9 @@ fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { c.error('unexpected `&`, expecting expression', node.right.pos) } } else if mut node.right is ast.SelectorExpr { + if node.right.expr.is_literal() { + c.error('cannot take the address of a literal value', node.pos.extend(node.right.pos)) + } right_sym := c.table.sym(right_type) expr_sym := c.table.sym(node.right.expr_type) if expr_sym.kind == .struct_ && (expr_sym.info as ast.Struct).is_minify @@ -3937,26 +4323,31 @@ fn (mut c Checker) prefix_expr(mut node ast.PrefixExpr) ast.Type { } } } - if node.op == .bit_not && !c.unwrap_generic(right_type).is_int() && !c.pref.translated - && !c.file.is_translated { - c.type_error_for_operator('~', 'integer', right_sym.name, node.pos) + if node.op == .bit_not && !c.pref.translated && !c.file.is_translated { + if right_sym.info is ast.Enum && !right_sym.info.is_flag { + c.error('operator `~` can only be used with `@[flag]` tagged enums', node.pos) + } + // Only check for int not enum as it is done above + if !right_sym.is_int() && right_sym.info !is ast.Enum { + c.type_error_for_operator('~', 'integer', right_sym.name, node.pos) + } } - if node.op == .not && right_type != ast.bool_type_idx && !c.pref.translated - && !c.file.is_translated { + if node.op == .not && right_sym.kind != .bool && !c.pref.translated && !c.file.is_translated { c.type_error_for_operator('!', 'bool', right_sym.name, node.pos) } // FIXME // there are currently other issues to investigate if right_type - // is unwraped directly as initialization, so do it here + // is unwrapped directly as initialization, so do it here if node.op == .minus && !right_sym.is_number() { c.type_error_for_operator('-', 'numeric', right_sym.name, node.pos) } if node.op == .arrow { - if right_sym.kind == .chan { - c.stmts_ending_with_expression(node.or_block.stmts) - return right_sym.chan_info().elem_type + raw_right_sym := c.table.final_sym(right_type) + if raw_right_sym.kind == .chan { + c.stmts_ending_with_expression(mut node.or_block.stmts) + return raw_right_sym.chan_info().elem_type } - c.type_error_for_operator('<-', '`chan`', right_sym.name, node.pos) + c.type_error_for_operator('<-', '`chan`', raw_right_sym.name, node.pos) } return right_type } @@ -3967,16 +4358,16 @@ fn (mut c Checker) type_error_for_operator(op_label string, types_label string, } fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_type ast.Type, pos token.Pos, range_index bool, is_gated bool) { - index_type_sym := c.table.sym(index_type) if typ_sym.kind in [.array, .array_fixed, .string] { + index_type_sym := c.table.sym(index_type) if !(index_type.is_int() || index_type_sym.kind == .enum_ || (index_type_sym.kind == .alias && (index_type_sym.info as ast.Alias).parent_type.is_int()) || (c.pref.translated && index_type.is_any_kind_of_pointer())) { type_str := if typ_sym.kind == .string { - 'non-integer string index `${index_type_sym.name}`' + 'non-integer string index `${c.table.type_to_str(index_type)}`' } else { - 'non-integer index `${index_type_sym.name}` (array type `${typ_sym.name}`)' + 'non-integer index `${c.table.type_to_str(index_type)}` (array type `${typ_sym.name}`)' } c.error('${type_str}', pos) } @@ -3991,7 +4382,7 @@ fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_ty } } } - if index_type.has_flag(.option) || index_type.has_flag(.result) { + if index_type.has_option_or_result() { type_str := if typ_sym.kind == .string { '(type `${typ_sym.name}`)' } else { @@ -4003,7 +4394,7 @@ fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_ty } fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { - mut typ := c.expr(node.left) + mut typ := c.expr(mut node.left) if typ == 0 { c.error('unknown type for expression `${node.left}`', node.pos) return typ @@ -4034,12 +4425,20 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { } else {} } - if typ_sym.kind !in [.array, .array_fixed, .string, .map] && !typ.is_ptr() - && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) { + is_aggregate_arr := typ_sym.kind == .aggregate + && (typ_sym.info as ast.Aggregate).types.filter(c.table.type_kind(it) !in [.array, .array_fixed, .string, .map]).len == 0 + if typ_sym.kind !in [.array, .array_fixed, .string, .map] + && (!typ.is_ptr() || typ_sym.kind in [.sum_type, .interface_]) + && typ !in [ast.byteptr_type, ast.charptr_type] && !typ.has_flag(.variadic) + && !is_aggregate_arr { c.error('type `${typ_sym.name}` does not support indexing', node.pos) } + if is_aggregate_arr { + // treating indexexpr of sumtype of array types + typ = (typ_sym.info as ast.Aggregate).types[0] + } if typ.has_flag(.option) { - if node.left is ast.Ident && (node.left as ast.Ident).or_expr.kind == .absent { + if node.left is ast.Ident && node.left.or_expr.kind == .absent { c.error('type `?${typ_sym.name}` is an Option, it must be unwrapped first; use `var?[]` to do it', node.left.pos()) } else if node.left is ast.CallExpr { @@ -4058,35 +4457,48 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { if !c.inside_unsafe && !c.is_builtin_mod && !c.inside_if_guard && !c.is_index_assign && typ_sym.kind == .map && node.or_expr.stmts.len == 0 { elem_type := c.table.value_type(typ) - if elem_type.is_real_pointer() { + if elem_type.is_any_kind_of_pointer() { c.note('accessing a pointer map value requires an `or {}` block outside `unsafe`', node.pos) } + mut checked_types := []ast.Type{} + if c.is_contains_any_kind_of_pointer(elem_type, mut checked_types) { + c.note('accessing map value that contain pointers requires an `or {}` block outside `unsafe`', + node.pos) + } } - if (typ.is_ptr() && !typ.has_flag(.shared_f) && !node.left.is_auto_deref_var()) + if (typ.is_ptr() && !typ.has_flag(.shared_f) && (!node.left.is_auto_deref_var() + || (typ_sym.kind == .struct_ && typ_sym.name != 'array'))) || typ.is_pointer() { mut is_ok := false + mut is_mut_struct := false if mut node.left is ast.Ident { if mut node.left.obj is ast.Var { // `mut param []T` function parameter is_ok = node.left.obj.is_mut && node.left.obj.is_arg && !typ.deref().is_ptr() + && typ_sym.kind != .struct_ + // `mut param Struct` + is_mut_struct = node.left.obj.is_mut && node.left.obj.is_arg + && typ_sym.kind == .struct_ } } if !is_ok && node.index is ast.RangeExpr { s := c.table.type_to_str(typ) c.error('type `${s}` does not support slicing', node.pos) + } else if is_mut_struct { + c.error('type `mut ${typ_sym.name}` does not support slicing', node.pos) } else if !c.inside_unsafe && !is_ok && !c.pref.translated && !c.file.is_translated { c.warn('pointer indexing is only allowed in `unsafe` blocks', node.pos) } } if mut node.index is ast.RangeExpr { // [1..2] if node.index.has_low { - index_type := c.expr(node.index.low) + index_type := c.expr(mut node.index.low) c.check_index(typ_sym, node.index.low, index_type, node.pos, true, node.is_gated) } if node.index.has_high { - index_type := c.expr(node.index.high) + index_type := c.expr(mut node.index.high) c.check_index(typ_sym, node.index.high, index_type, node.pos, true, node.is_gated) } // array[1..2] => array @@ -4102,7 +4514,7 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { if typ_sym.kind == .map { info := typ_sym.info as ast.Map c.expected_type = info.key_type - index_type := c.expr(node.index) + index_type := c.expr(mut node.index) if !c.check_types(index_type, info.key_type) { err := c.expected_msg(index_type, info.key_type) c.error('invalid key: ${err}', node.pos) @@ -4114,7 +4526,7 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { node.pos) } } else { - index_type := c.expr(node.index) + index_type := c.expr(mut node.index) // for [1] case #[1] is not allowed! if node.is_gated == true { c.error('`#[]` allowed only for ranges', node.pos) @@ -4129,8 +4541,8 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { if node.or_expr.stmts.len > 0 && node.or_expr.stmts.last() is ast.ExprStmt { c.expected_or_type = typ } - c.stmts_ending_with_expression(node.or_expr.stmts) - c.check_expr_opt_call(node, typ) + c.stmts_ending_with_expression(mut node.or_expr.stmts) + c.check_expr_option_or_result_call(node, typ) return typ } @@ -4185,7 +4597,13 @@ fn (mut c Checker) enum_val(mut node ast.EnumVal) ast.Type { fsym := c.table.final_sym(typ) if fsym.kind != .enum_ && !c.pref.translated && !c.file.is_translated { // TODO in C int fields can be compared to enums, need to handle that in C2V - c.error('expected type is not an enum (`${typ_sym.name}`)', node.pos) + if typ_sym.kind == .placeholder { + // If it's a placeholder, the type doesn't exist, print + // an error that makes sense here. + c.error('unknown type `${typ_sym.name}`', node.pos) + } else { + c.error('expected type is not an enum (`${typ_sym.name}`)', node.pos) + } return ast.void_type } if fsym.info !is ast.Enum { @@ -4216,7 +4634,7 @@ fn (mut c Checker) chan_init(mut node ast.ChanInit) ast.Type { } } if node.has_cap { - c.check_array_init_para_type('cap', node.cap_expr, node.pos) + c.check_array_init_para_type('cap', mut node.cap_expr, node.pos) } return node.typ } else { @@ -4260,54 +4678,13 @@ fn (mut c Checker) check_dup_keys(node &ast.MapInit, i int) { } } -// call this *before* calling error or warn -fn (mut c Checker) add_error_detail(s string) { - c.error_details << s -} - -fn (mut c Checker) add_error_detail_with_pos(msg string, pos token.Pos) { - c.add_error_detail(util.formatted_error('details:', msg, c.file.path, pos)) -} - -fn (mut c Checker) add_instruction_for_option_type() { - c.add_error_detail_with_pos('prepend ? before the declaration of the return type of `${c.table.cur_fn.name}`', - c.table.cur_fn.return_type_pos) -} - -fn (mut c Checker) add_instruction_for_result_type() { - c.add_error_detail_with_pos('prepend ! before the declaration of the return type of `${c.table.cur_fn.name}`', - c.table.cur_fn.return_type_pos) -} - -fn (mut c Checker) warn(s string, pos token.Pos) { - allow_warnings := !(c.pref.is_prod || c.pref.warns_are_errors) // allow warnings only in dev builds - c.warn_or_error(s, pos, allow_warnings) -} - -fn (mut c Checker) error(message string, pos token.Pos) { - $if checker_exit_on_first_error ? { - eprintln('\n\n>> checker error: ${message}, pos: ${pos}') - print_backtrace() - exit(1) - } - if (c.pref.translated || c.file.is_translated) && message.starts_with('mismatched types') { - // TODO move this - return - } - if c.pref.is_verbose { - print_backtrace() - } - msg := message.replace('`Array_', '`[]') - c.warn_or_error(msg, pos, false) -} - fn (c &Checker) check_struct_signature_init_fields(from ast.Struct, to ast.Struct, node ast.StructInit) bool { - if node.fields.len == 0 { + if node.init_fields.len == 0 { return from.fields.len == to.fields.len } mut count_not_in_from := 0 - for field in node.fields { + for field in node.init_fields { filtered := from.fields.filter(it.name == field.name) if filtered.len != 1 { count_not_in_from++ @@ -4331,7 +4708,7 @@ fn (c &Checker) check_struct_signature(from ast.Struct, to ast.Struct) bool { } counterpart := filtered[0] if field.typ != counterpart.typ { - // field has different tye + // field has different type return false } if field.is_pub != counterpart.is_pub { @@ -4346,97 +4723,10 @@ fn (c &Checker) check_struct_signature(from ast.Struct, to ast.Struct) bool { return true } -fn (mut c Checker) note(message string, pos token.Pos) { - if c.pref.message_limit >= 0 && c.nr_notices >= c.pref.message_limit { - c.should_abort = true - return - } - if c.is_generated { - return - } - mut details := '' - if c.error_details.len > 0 { - details = c.error_details.join('\n') - c.error_details = [] - } - wrn := errors.Notice{ - reporter: errors.Reporter.checker - pos: pos - file_path: c.file.path - message: message - details: details - } - c.file.notices << wrn - c.notices << wrn - c.nr_notices++ -} - -fn (mut c Checker) warn_or_error(message string, pos token.Pos, warn bool) { - // add backtrace to issue struct, how? - // if c.pref.is_verbose { - // print_backtrace() - // } - mut details := '' - if c.error_details.len > 0 { - details = c.error_details.join('\n') - c.error_details = [] - } - if warn && !c.pref.skip_warnings { - c.nr_warnings++ - if c.pref.message_limit >= 0 && c.nr_warnings >= c.pref.message_limit { - c.should_abort = true - return - } - wrn := errors.Warning{ - reporter: errors.Reporter.checker - pos: pos - file_path: c.file.path - message: message - details: details - } - c.file.warnings << wrn - c.warnings << wrn - return - } - if !warn { - if c.pref.fatal_errors { - util.show_compiler_message('error:', errors.CompilerMessage{ - pos: pos - file_path: c.file.path - message: message - details: details - }) - exit(1) - } - c.nr_errors++ - if c.pref.message_limit >= 0 && c.errors.len >= c.pref.message_limit { - c.should_abort = true - return - } - if pos.line_nr !in c.error_lines { - err := errors.Error{ - reporter: errors.Reporter.checker - pos: pos - file_path: c.file.path - message: message - details: details - } - c.file.errors << err - c.errors << err - c.error_lines << pos.line_nr - } - } -} - -// for debugging only -fn (c &Checker) fileis(s string) bool { - return c.file.path.contains(s) -} - fn (mut c Checker) fetch_field_name(field ast.StructField) string { mut name := field.name for attr in field.attrs { - if attr.kind == .string && attr.name == 'sql' && attr.arg != '' { + if attr.kind == .string && attr.arg != '' && attr.name == 'sql' { name = attr.arg break } @@ -4448,16 +4738,10 @@ fn (mut c Checker) fetch_field_name(field ast.StructField) string { return name } -fn (mut c Checker) trace(fbase string, message string) { - if c.file.path_base == fbase { - println('> c.trace | ${fbase:-10s} | ${message}') - } -} - -fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos token.Pos) ? { +fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos token.Pos) bool { if typ == 0 { c.error('unknown type', pos) - return none + return false } c.ensure_generic_type_level++ @@ -4467,7 +4751,7 @@ fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos toke if c.ensure_generic_type_level > checker.expr_level_cutoff_limit { c.error('checker: too many levels of Checker.ensure_generic_type_specify_type_names calls: ${c.ensure_generic_type_level} ', pos) - return none + return false } sym := c.table.final_sym(typ) @@ -4480,29 +4764,42 @@ fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos toke match sym.kind { .function { fn_info := sym.info as ast.FnType - c.ensure_generic_type_specify_type_names(fn_info.func.return_type, fn_info.func.return_type_pos)? + if !c.ensure_generic_type_specify_type_names(fn_info.func.return_type, fn_info.func.return_type_pos) { + return false + } for param in fn_info.func.params { - c.ensure_generic_type_specify_type_names(param.typ, param.type_pos)? + if !c.ensure_generic_type_specify_type_names(param.typ, param.type_pos) { + return false + } } } .array { - c.ensure_generic_type_specify_type_names((sym.info as ast.Array).elem_type, - pos)? + if !c.ensure_generic_type_specify_type_names((sym.info as ast.Array).elem_type, + pos) { + return false + } } .array_fixed { - c.ensure_generic_type_specify_type_names((sym.info as ast.ArrayFixed).elem_type, - pos)? + if !c.ensure_generic_type_specify_type_names((sym.info as ast.ArrayFixed).elem_type, + pos) { + return false + } } .map { info := sym.info as ast.Map - c.ensure_generic_type_specify_type_names(info.key_type, pos)? - c.ensure_generic_type_specify_type_names(info.value_type, pos)? + if !c.ensure_generic_type_specify_type_names(info.key_type, pos) { + return false + } + if !c.ensure_generic_type_specify_type_names(info.value_type, pos) { + return false + } } .sum_type { info := sym.info as ast.SumType if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 { c.error('`${sym.name}` type is generic sumtype, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]', pos) + return false } } .struct_ { @@ -4510,6 +4807,7 @@ fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos toke if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 { c.error('`${sym.name}` type is generic struct, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]', pos) + return false } } .interface_ { @@ -4517,34 +4815,51 @@ fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos toke if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 { c.error('`${sym.name}` type is generic interface, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]', pos) + return false } } else {} } + return true } -fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Pos) ? { +fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Pos) bool { if typ == 0 { c.error('unknown type', pos) - return + return false } sym := c.table.sym(typ) if !c.is_builtin_mod && sym.kind == .struct_ && !sym.is_pub && sym.mod != c.mod { c.error('struct `${sym.name}` was declared as private to module `${sym.mod}`, so it can not be used inside module `${c.mod}`', pos) - return + return false } match sym.kind { .placeholder { - if sym.language == .v && !sym.name.starts_with('C.') { + // if sym.language == .c && sym.name == 'C.time_t' { + // TODO temporary hack until we can define C aliases + // return true + //} + // if sym.language == .v && !sym.name.starts_with('C.') { + // if sym.language in [.v, .c] { + if sym.language == .v { c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `${sym.name}`'), pos) - return + return false + } else if sym.language == .c { + if !c.pref.translated && !c.file.is_translated { + c.warn(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `${sym.name}` (all virtual C types must be defined, this will be an error soon)'), + pos) + } + // dump(sym) + // for _, t in c.table.type_symbols { + // println(t.name) + //} } } .int_literal, .float_literal { // Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different - // suggestions due to f32 comparision issue. + // suggestions due to f32 comparison issue. if !c.is_builtin_mod { msg := if sym.kind == .int_literal { 'unknown type `${sym.name}`.\nDid you mean `int`?' @@ -4552,38 +4867,54 @@ fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Pos) ? { 'unknown type `${sym.name}`.\nDid you mean `f64`?' } c.error(msg, pos) - return + return false } } .function { fn_info := sym.info as ast.FnType - c.ensure_type_exists(fn_info.func.return_type, fn_info.func.return_type_pos)? + if !c.ensure_type_exists(fn_info.func.return_type, fn_info.func.return_type_pos) { + return false + } for param in fn_info.func.params { - c.ensure_type_exists(param.typ, param.type_pos)? + if !c.ensure_type_exists(param.typ, param.type_pos) { + return false + } } } .array { - c.ensure_type_exists((sym.info as ast.Array).elem_type, pos)? + if !c.ensure_type_exists((sym.info as ast.Array).elem_type, pos) { + return false + } } .array_fixed { - c.ensure_type_exists((sym.info as ast.ArrayFixed).elem_type, pos)? + if !c.ensure_type_exists((sym.info as ast.ArrayFixed).elem_type, pos) { + return false + } } .map { info := sym.info as ast.Map - c.ensure_type_exists(info.key_type, pos)? - c.ensure_type_exists(info.value_type, pos)? + if !c.ensure_type_exists(info.key_type, pos) { + return false + } + if !c.ensure_type_exists(info.value_type, pos) { + return false + } } .sum_type { info := sym.info as ast.SumType for concrete_typ in info.concrete_types { - c.ensure_type_exists(concrete_typ, pos)? + if !c.ensure_type_exists(concrete_typ, pos) { + return false + } } } else {} } + return true } -fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) { +// return true if a violation of a shared variable access rule is detected +fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) bool { mut pos := token.Pos{} match expr { ast.Ident { @@ -4592,9 +4923,10 @@ fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) action := if what == 'argument' { 'passed' } else { 'used' } c.error('`${expr.name}` is `shared` and must be `rlock`ed or `lock`ed to be ${action} as non-mut ${what}', expr.pos) + return true } } - return + return false } ast.SelectorExpr { pos = expr.pos @@ -4604,31 +4936,42 @@ fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) action := if what == 'argument' { 'passed' } else { 'used' } c.error('`${expr_name}` is `shared` and must be `rlock`ed or `lock`ed to be ${action} as non-mut ${what}', expr.pos) + return true } - return + return false } else { - c.fail_if_unreadable(expr.expr, expr.expr_type, what) + if c.fail_if_unreadable(expr.expr, expr.expr_type, what) { + return true + } } } ast.CallExpr { pos = expr.pos if expr.is_method { - c.fail_if_unreadable(expr.left, expr.left_type, what) + if c.fail_if_unreadable(expr.left, expr.left_type, what) { + return true + } } - return + return false } ast.LockExpr { // TODO: check expressions inside the lock by appending to c.(r)locked_names - return + return false } ast.IndexExpr { pos = expr.left.pos().extend(expr.pos) - c.fail_if_unreadable(expr.left, expr.left_type, what) + if c.fail_if_unreadable(expr.left, expr.left_type, what) { + return true + } } ast.InfixExpr { pos = expr.left.pos().extend(expr.pos) - c.fail_if_unreadable(expr.left, expr.left_type, what) - c.fail_if_unreadable(expr.right, expr.right_type, what) + if c.fail_if_unreadable(expr.left, expr.left_type, what) { + return true + } + if c.fail_if_unreadable(expr.right, expr.right_type, what) { + return true + } } else { pos = expr.pos() @@ -4637,6 +4980,29 @@ fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) if typ.has_flag(.shared_f) { c.error('you have to create a handle and `rlock` it to use a `shared` element as non-mut ${what}', pos) + return true + } + return false +} + +fn (mut c Checker) fail_if_stack_struct_action_outside_unsafe(mut ident ast.Ident, failed_action string) { + if mut ident.obj is ast.Var { + mut obj := unsafe { &ident.obj } + if c.fn_scope != unsafe { nil } { + obj = c.fn_scope.find_var(ident.obj.name) or { obj } + } + if obj.is_stack_obj && !c.inside_unsafe { + sym := c.table.sym(obj.typ.set_nr_muls(0)) + if !sym.is_heap() && !c.pref.translated && !c.file.is_translated { + suggestion := if sym.kind == .struct_ { + 'declaring `${sym.name}` as `[heap]`' + } else { + 'wrapping the `${sym.name}` object in a `struct` declared as `[heap]`' + } + c.error('`${ident.name}` cannot be ${failed_action} outside `unsafe` blocks as it might refer to an object stored on stack. Consider ${suggestion}.', + ident.pos) + } + } } } @@ -4653,7 +5019,10 @@ fn (mut c Checker) goto_stmt(node ast.GotoStmt) { c.error('goto is not allowed in defer statements', node.pos) } if !c.inside_unsafe { - c.warn('`goto` requires `unsafe` (consider using labelled break/continue)', node.pos) + if !c.pref.translated && !c.file.is_translated { + c.warn('`goto` requires `unsafe` (consider using labelled break/continue)', + node.pos) + } } if c.table.cur_fn != unsafe { nil } && node.name !in c.table.cur_fn.label_names { c.error('unknown label `${node.name}`', node.pos) @@ -4672,45 +5041,6 @@ fn (mut c Checker) check_unused_labels() { } } -fn (mut c Checker) deprecate(kind string, name string, attrs []ast.Attr, pos token.Pos) { - mut deprecation_message := '' - now := time.now() - mut after_time := now - for attr in attrs { - if attr.name == 'deprecated' && attr.arg != '' { - deprecation_message = attr.arg - } - if attr.name == 'deprecated_after' && attr.arg != '' { - after_time = time.parse_iso8601(attr.arg) or { - c.error('invalid time format', attr.pos) - now - } - } - } - start_message := '${kind} `${name}`' - error_time := after_time.add_days(180) - if error_time < now { - c.error(semicolonize('${start_message} has been deprecated since ${after_time.ymmdd()}', - deprecation_message), pos) - } else if after_time < now { - c.warn(semicolonize('${start_message} has been deprecated since ${after_time.ymmdd()}, it will be an error after ${error_time.ymmdd()}', - deprecation_message), pos) - } else if after_time == now { - c.warn(semicolonize('${start_message} has been deprecated', deprecation_message), - pos) - } else { - c.note(semicolonize('${start_message} will be deprecated after ${after_time.ymmdd()}, and will become an error after ${error_time.ymmdd()}', - deprecation_message), pos) - } -} - -fn semicolonize(main string, details string) string { - if details == '' { - return main - } - return '${main}; ${details}' -} - fn (mut c Checker) deprecate_old_isreftype_and_sizeof_of_a_guessed_type(is_guessed_type bool, typ ast.Type, pos token.Pos, label string) { if is_guessed_type { styp := c.table.type_to_str(typ) diff --git a/tests/testdata/benchmarks/inlay_hints.v b/tests/testdata/benchmarks/inlay_hints.v index 762b65c0..43f986ce 100644 --- a/tests/testdata/benchmarks/inlay_hints.v +++ b/tests/testdata/benchmarks/inlay_hints.v @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. +// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module builtin @@ -77,7 +77,7 @@ pub fn (s string) runes() []rune { // cstring_to_vstring creates a new V string copy of the C style string, // pointed by `s`. This function is most likely what you want to use when // working with C style pointers to 0 terminated strings (i.e. `char*`). -// It is recomended to use it, unless you *do* understand the implications of +// It is recommended to use it, unless you *do* understand the implications of // tos/tos2/tos3/tos4/tos5 in terms of memory management and interactions with // -autofree and `[manualfree]`. // It will panic, if the pointer `s` is 0. @@ -313,9 +313,16 @@ fn (a string) clone_static() string { return a.clone() } +// option_clone_static returns an independent copy of a given array when lhs is an option type. +// It should be used only in -autofree generated code. +@[inline; markused] +fn (a string) option_clone_static() ?string { + return ?string(a.clone()) +} + // clone returns a copy of the V string `a`. pub fn (a string) clone() string { - if a.len == 0 { + if a.len <= 0 { return '' } mut b := string{ @@ -329,7 +336,7 @@ pub fn (a string) clone() string { return b } -// replace_once replaces the first occurence of `rep` with the string passed in `with`. +// replace_once replaces the first occurrence of `rep` with the string passed in `with`. pub fn (s string) replace_once(rep string, with string) string { idx := s.index_(rep) if idx == -1 { @@ -338,7 +345,7 @@ pub fn (s string) replace_once(rep string, with string) string { return s.substr(0, idx) + with + s.substr(idx + rep.len, s.len) } -// replace replaces all occurences of `rep` with the string passed in `with`. +// replace replaces all occurrences of `rep` with the string passed in `with`. @[direct_array_access] pub fn (s string) replace(rep string, with string) string { if s.len == 0 || rep.len == 0 || rep.len > s.len { @@ -406,7 +413,7 @@ struct RepIndex { val_idx int } -// replace_each replaces all occurences of the string pairs given in `vals`. +// replace_each replaces all occurrences of the string pairs given in `vals`. // Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC' @[direct_array_access] pub fn (s string) replace_each(vals []string) string { @@ -496,7 +503,7 @@ pub fn (s string) replace_each(vals []string) string { } } -// replace_char replaces all occurences of the character `rep` multiple occurences of the character passed in `with` with respect to `repeat`. +// replace_char replaces all occurrences of the character `rep` multiple occurrences of the character passed in `with` with respect to `repeat`. // Example: assert '\tHello!'.replace_char(`\t`,` `,8) == ' Hello!' @[direct_array_access] pub fn (s string) replace_char(rep u8, with u8, repeat int) string { @@ -613,6 +620,59 @@ pub fn (s string) u8() u8 { return u8(strconv.common_parse_uint(s, 0, 8, false, false) or { 0 }) } +// u8_array returns the value of the hex/bin string as u8 array. +// hex string example: `'0x11223344ee'.u8_array() == [u8(0x11),0x22,0x33,0x44,0xee]`. +// bin string example: `'0b1101_1101'.u8_array() == [u8(0xdd)]`. +// underscore in the string will be stripped. +pub fn (s string) u8_array() []u8 { + // strip underscore in the string + mut tmps := s.replace('_', '') + if tmps.len == 0 { + return []u8{} + } + tmps = tmps.to_lower() + if tmps.starts_with('0x') { + tmps = tmps[2..] + if tmps.len == 0 { + return []u8{} + } + // make sure every digit is valid hex digit + if !tmps.contains_only('0123456789abcdef') { + return []u8{} + } + // make sure tmps has even hex digits + if tmps.len % 2 == 1 { + tmps = '0' + tmps + } + + mut ret := []u8{len: tmps.len / 2} + for i in 0 .. ret.len { + ret[i] = u8(tmps[2 * i..2 * i + 2].parse_uint(16, 8) or { 0 }) + } + return ret + } else if tmps.starts_with('0b') { + tmps = tmps[2..] + if tmps.len == 0 { + return []u8{} + } + // make sure every digit is valid binary digit + if !tmps.contains_only('01') { + return []u8{} + } + // make sure tmps has multiple of 8 binary digits + if tmps.len % 8 != 0 { + tmps = '0'.repeat(8 - tmps.len % 8) + tmps + } + + mut ret := []u8{len: tmps.len / 8} + for i in 0 .. ret.len { + ret[i] = u8(tmps[8 * i..8 * i + 8].parse_uint(2, 8) or { 0 }) + } + return ret + } + return []u8{} +} + // u16 returns the value of the string as u16 `'1'.u16() == u16(1)`. @[inline] pub fn (s string) u16() u16 { @@ -745,7 +805,7 @@ pub fn (s string) split_any(delim string) []string { mut i := 0 // check empty source string if s.len > 0 { - // if empty delimiter string using defautl split + // if empty delimiter string using default split if delim.len <= 0 { return s.split('') } @@ -795,7 +855,7 @@ pub fn (s string) rsplit_any(delim string) []string { return res } -// split splits the string to an array by `delim`. +// split splits the string into an array of strings at the given delimiter. // Example: assert 'A B C'.split(' ') == ['A','B','C'] // If `delim` is empty the string is split by it's characters. // Example: assert 'DEF'.split('') == ['D','E','F'] @@ -804,7 +864,7 @@ pub fn (s string) split(delim string) []string { return s.split_nth(delim, 0) } -// rsplit splits the string to an array by `delim` in reverse order. +// rsplit splits the string into an array of strings at the given delimiter, starting from the right. // Example: assert 'A B C'.rsplit(' ') == ['C','B','A'] // If `delim` is empty the string is split by it's characters. // Example: assert 'DEF'.rsplit('') == ['F','E','D'] @@ -813,15 +873,12 @@ pub fn (s string) rsplit(delim string) []string { return s.rsplit_nth(delim, 0) } -// split_once devides string into pair of string by `delim`. +// split_once splits the string into a pair of strings at the given delimiter. // Example: // ```v -// path, ext := 'file.ts.dts'.splice_once('.')? +// path, ext := 'file.ts.dts'.split_once('.')? // assert path == 'file' // assert ext == 'ts.dts' -// ``` -// Note that rsplit_once returns splitted string string as first part of pair, -// and returns remaining as second part of pair. pub fn (s string) split_once(delim string) ?(string, string) { result := s.split_nth(delim, 2) @@ -832,15 +889,14 @@ pub fn (s string) split_once(delim string) ?(string, string) { return result[0], result[1] } -// rsplit_once devides string into pair of string by `delim`. +// rsplit_once splits the string into a pair of strings at the given delimiter, starting from the right. // Example: // ```v -// path, ext := 'file.ts.dts'.splice_once('.')? +// path, ext := 'file.ts.dts'.rsplit_once('.')? // assert path == 'file.ts' // assert ext == 'dts' // ``` -// Note that rsplit_once returns remaining string as first part of pair, -// and returns splitted string as second part of pair. +// NOTE: rsplit_once returns the string at the left side of the delimiter as first part of the pair. pub fn (s string) rsplit_once(delim string) ?(string, string) { result := s.rsplit_nth(delim, 2) @@ -865,7 +921,7 @@ pub fn (s string) split_nth(delim string, nth int) []string { i = 1 for ch in s { if nth > 0 && i >= nth { - res << s[i..] + res << s[i - 1..] break } res << ch.ascii_str() @@ -900,7 +956,7 @@ pub fn (s string) split_nth(delim string, nth int) []string { } else { mut start := 0 - // Take the left part for each delimiter occurence + // Take the left part for each delimiter occurrence for i <= s.len { is_delim := i + delim.len <= s.len && s.substr(i, i + delim.len) == delim if is_delim { @@ -938,7 +994,7 @@ pub fn (s string) rsplit_nth(delim string, nth int) []string { 0 { for i >= 0 { if nth > 0 && res.len == nth - 1 { - res << s[..i] + res << s[..i + 1] break } res << s[i].ascii_str() @@ -1027,17 +1083,11 @@ pub fn (s string) split_into_lines() []string { return res } -// used internally for [2..4] -@[inline] -fn (s string) substr2(start int, _end int, end_max bool) string { - end := if end_max { s.len } else { _end } - return s.substr(start, end) -} - // substr returns the string between index positions `start` and `end`. // Example: assert 'ABCD'.substr(1,3) == 'BC' @[direct_array_access] -pub fn (s string) substr(start int, end int) string { +pub fn (s string) substr(start int, _end int) string { + end := if _end == max_int { s.len } else { _end } // max_int $if !no_bounds_checking { if start > end || start > s.len || end > s.len || start < 0 || end < 0 { panic('substr(${start}, ${end}) out of bounds (len=${s.len})') @@ -1058,10 +1108,25 @@ pub fn (s string) substr(start int, end int) string { return res } +// substr_unsafe works like substr(), but doesn't copy (allocate) the substring +@[direct_array_access] +pub fn (s string) substr_unsafe(start int, _end int) string { + end := if _end == 2147483647 { s.len } else { _end } // max_int + len := end - start + if len == s.len { + return s + } + return string{ + str: unsafe { s.str + start } + len: len + } +} + // version of `substr()` that is used in `a[start..end] or {` // return an error when the index is out of range @[direct_array_access] -pub fn (s string) substr_with_check(start int, end int) !string { +pub fn (s string) substr_with_check(start int, _end int) !string { + end := if _end == max_int { s.len } else { _end } // max_int if start > end || start > s.len || end > s.len || start < 0 || end < 0 { return error('substr(${start}, ${end}) out of bounds (len=${s.len})') } @@ -1085,7 +1150,7 @@ pub fn (s string) substr_with_check(start int, end int) !string { @[direct_array_access] pub fn (s string) substr_ni(_start int, _end int) string { mut start := _start - mut end := _end + mut end := if _end == max_int { s.len } else { _end } // max_int // borders math if start < 0 { @@ -1147,8 +1212,8 @@ fn (s string) index_(p string) int { return -1 } -// index returns the position of the first character of the input string. -// It will return `none` if the input string can't be found. +// index returns the position of the first character of the first occurrence of the `needle` string in `s`. +// It will return `none` if the `needle` string can't be found in `s`. pub fn (s string) index(p string) ?int { idx := s.index_(p) if idx == -1 { @@ -1157,6 +1222,23 @@ pub fn (s string) index(p string) ?int { return idx } +// index_last returns the position of the first character of the *last* occurrence of the `needle` string in `s`. +pub fn (s string) index_last(needle string) ?int { + idx := s.index_last_(needle) + if idx == -1 { + return none + } + return idx +} + +// last_index returns the position of the first character of the *last* occurrence of the `needle` string in `s`. +@[deprecated: 'use `.index_last(needle string)` instead'] +@[deprecated_after: '2023-12-18'] +@[inline] +pub fn (s string) last_index(needle string) ?int { + return s.index_last(needle) +} + // index_kmp does KMP search. @[direct_array_access; manualfree] fn (s string) index_kmp(p string) int { @@ -1204,9 +1286,9 @@ pub fn (s string) index_any(chars string) int { return -1 } -// last_index returns the position of the last occurence of the input string. +// index_last_ returns the position of the last occurrence of the given string `p` in `s`. @[direct_array_access] -fn (s string) last_index_(p string) int { +fn (s string) index_last_(p string) int { if p.len > s.len || p.len == 0 { return -1 } @@ -1224,15 +1306,6 @@ fn (s string) last_index_(p string) int { return -1 } -// last_index returns the position of the last occurence of the input string. -pub fn (s string) last_index(p string) ?int { - idx := s.last_index_(p) - if idx == -1 { - return none - } - return idx -} - // index_after returns the position of the input string, starting search from `start` position. @[direct_array_access] pub fn (s string) index_after(p string, start int) int { @@ -1274,10 +1347,10 @@ pub fn (s string) index_u8(c u8) int { return -1 } -// last_index_byte returns the index of the last occurence of byte `c` if found in the string. -// last_index_byte returns -1 if the byte is not found. +// index_u8_last returns the index of the *last* occurrence of the byte `c` (if found) in the string. +// It returns -1, if `c` is not found. @[direct_array_access] -pub fn (s string) last_index_u8(c u8) int { +pub fn (s string) index_u8_last(c u8) int { for i := s.len - 1; i >= 0; i-- { if unsafe { s.str[i] == c } { return i @@ -1286,6 +1359,15 @@ pub fn (s string) last_index_u8(c u8) int { return -1 } +// last_index_u8 returns the index of the last occurrence of byte `c` if found in the string. +// It returns -1, if the byte `c` is not found. +@[deprecated: 'use `.index_u8_last(c u8)` instead'] +@[deprecated_after: '2023-12-18'] +@[inline] +pub fn (s string) last_index_u8(c u8) int { + return s.index_u8_last(c) +} + // count returns the number of occurrences of `substr` in the string. // count returns -1 if no `substr` could be found. @[direct_array_access] @@ -1432,7 +1514,7 @@ pub fn (s string) to_lower() string { } } -// is_lower returns `true` if all characters in the string is lowercase. +// is_lower returns `true` if all characters in the string are lowercase. // Example: assert 'hello developer'.is_lower() == true @[direct_array_access] pub fn (s string) is_lower() bool { @@ -1462,7 +1544,7 @@ pub fn (s string) to_upper() string { } } -// is_upper returns `true` if all characters in the string is uppercase. +// is_upper returns `true` if all characters in the string are uppercase. // See also: [`byte.is_capital`](#byte.is_capital) // Example: assert 'HELLO V'.is_upper() == true @[direct_array_access] @@ -1493,6 +1575,24 @@ pub fn (s string) capitalize() string { return res } +// uncapitalize returns the string with the first character uncapitalized. +// Example: assert 'Hello, Bob!'.uncapitalize() == 'hello, Bob!' +@[direct_array_access] +pub fn (s string) uncapitalize() string { + if s.len == 0 { + return '' + } + s0 := s[0] + letter := s0.ascii_str() + uletter := letter.to_lower() + if s.len == 1 { + return uletter + } + srest := s[1..] + res := uletter + srest + return res +} + // is_capital returns `true`, if the first character in the string `s`, // is a capital letter, and the rest are NOT. // Example: assert 'Hello'.is_capital() == true @@ -1579,7 +1679,7 @@ pub fn (s string) trim(cutset string) string { return s.substr(left, right) } -// trim_indexes gets the new start and end indicies of a string when any of the characters given in `cutset` were stripped from the start and end of the string. Should be used as an input to `substr()`. If the string contains only the characters in `cutset`, both values returned are zero. +// trim_indexes gets the new start and end indices of a string when any of the characters given in `cutset` were stripped from the start and end of the string. Should be used as an input to `substr()`. If the string contains only the characters in `cutset`, both values returned are zero. // Example: left, right := '-hi-'.trim_indexes('-') @[direct_array_access] pub fn (s string) trim_indexes(cutset string) (int, int) { @@ -1677,22 +1777,6 @@ pub fn (s string) trim_string_right(str string) string { return s.clone() } -// trim_prefix strips `str` from the start of the string. -// Example: assert 'WorldHello V'.trim_prefix('World') == 'Hello V' -@[deprecated: 'use s.trim_string_left(x) instead'] -@[deprecated_after: '2022-01-19'] -pub fn (s string) trim_prefix(str string) string { - return s.trim_string_left(str) -} - -// trim_suffix strips `str` from the end of the string. -// Example: assert 'Hello VWorld'.trim_suffix('World') == 'Hello V' -@[deprecated: 'use s.trim_string_right(x) instead'] -@[deprecated_after: '2022-01-19'] -pub fn (s string) trim_suffix(str string) string { - return s.trim_string_right(str) -} - // compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`. pub fn compare_strings(a &string, b &string) int { if a < b { @@ -1722,13 +1806,13 @@ fn compare_lower_strings(a &string, b &string) int { return compare_strings(&aa, &bb) } -// sort_ignore_case sorts the string array using case insesitive comparing. +// sort_ignore_case sorts the string array using case insensitive comparing. @[inline] pub fn (mut s []string) sort_ignore_case() { s.sort_with_compare(compare_lower_strings) } -// sort_by_len sorts the the string array by each string's `.len` length. +// sort_by_len sorts the string array by each string's `.len` length. @[inline] pub fn (mut s []string) sort_by_len() { s.sort_with_compare(compare_strings_by_len) @@ -1742,15 +1826,13 @@ pub fn (s string) str() string { // at returns the byte at index `idx`. // Example: assert 'ABC'.at(1) == u8(`B`) -fn (s string) at(idx int) byte { +fn (s string) at(idx int) u8 { $if !no_bounds_checking { if idx < 0 || idx >= s.len { panic('string index out of range: ${idx} / ${s.len}') } } - unsafe { - return s.str[idx] - } + return unsafe { s.str[idx] } } // version of `at()` that is used in `a[i] or {` @@ -1764,6 +1846,165 @@ fn (s string) at_with_check(idx int) ?u8 { } } +// Check if a string is an octal value. Returns 'true' if it is, or 'false' if it is not +@[direct_array_access] +pub fn (str string) is_oct() bool { + mut i := 0 + + if str.len == 0 { + return false + } + + if str[i] == `0` { + i++ + } else if str[i] == `-` || str[i] == `+` { + i++ + + if str[i] == `0` { + i++ + } else { + return false + } + } else { + return false + } + + if str[i] == `o` { + i++ + } else { + return false + } + + if i == str.len { + return false + } + + for i < str.len { + if str[i] < `0` || str[i] > `7` { + return false + } + i++ + } + + return true +} + +// is_bin returns `true` if the string is a binary value. +@[direct_array_access] +pub fn (str string) is_bin() bool { + mut i := 0 + + if str.len == 0 { + return false + } + + if str[i] == `0` { + i++ + } else if str[i] == `-` || str[i] == `+` { + i++ + + if str[i] == `0` { + i++ + } else { + return false + } + } else { + return false + } + + if str[i] == `b` { + i++ + } else { + return false + } + + if i == str.len { + return false + } + + for i < str.len { + if str[i] < `0` || str[i] > `1` { + return false + } + i++ + } + + return true +} + +// is_hex returns 'true' if the string is a hexadecimal value. +@[direct_array_access] +pub fn (str string) is_hex() bool { + mut i := 0 + + if str.len == 0 { + return false + } + + if str[i] == `0` { + i++ + } else if str[i] == `-` || str[i] == `+` { + i++ + + if str[i] == `0` { + i++ + } else { + return false + } + } else { + return false + } + + if str[i] == `x` { + i++ + } else { + return false + } + + if i == str.len { + return false + } + + for i < str.len { + if (str[i] < `0` || str[i] > `9`) && ((str[i] < `a` || str[i] > `f`) + && (str[i] < `A` || str[i] > `F`)) { + return false + } + i++ + } + + return true +} + +// Check if a string is an integer value. Returns 'true' if it is, or 'false' if it is not +@[direct_array_access] +pub fn (str string) is_int() bool { + mut i := 0 + + if str.len == 0 { + return false + } + + if (str[i] != `-` && str[i] != `+`) && (!str[i].is_digit()) { + return false + } else { + i++ + } + + if i == str.len && (!str[i - 1].is_digit()) { + return false + } + + for i < str.len { + if str[i] < `0` || str[i] > `9` { + return false + } + i++ + } + + return true +} + // is_space returns `true` if the byte is a white space character. // The following list is considered white space characters: ` `, `\t`, `\n`, `\v`, `\f`, `\r`, 0x85, 0xa0 // Example: assert u8(` `).is_space() == true @@ -1838,6 +2079,7 @@ pub fn (s &string) free() { unsafe { // C.printf(c's: %x %s\n', s.str, s.str) free(s.str) + s.str = nil } s.is_lit = -98761234 } @@ -1868,12 +2110,12 @@ pub fn (s string) all_before(sub string) string { return s[..pos] } -// all_before_last returns the contents before the last occurence of `sub` in the string. +// all_before_last returns the contents before the last occurrence of `sub` in the string. // If the substring is not found, it returns the full input string. // Example: assert '23:34:45.234'.all_before_last(':') == '23:34' // Example: assert 'abcd'.all_before_last('.') == 'abcd' pub fn (s string) all_before_last(sub string) string { - pos := s.last_index_(sub) + pos := s.index_last_(sub) if pos == -1 { return s.clone() } @@ -1892,19 +2134,19 @@ pub fn (s string) all_after(sub string) string { return s[pos + sub.len..] } -// all_after_last returns the contents after the last occurence of `sub` in the string. +// all_after_last returns the contents after the last occurrence of `sub` in the string. // If the substring is not found, it returns the full input string. // Example: assert '23:34:45.234'.all_after_last(':') == '45.234' // Example: assert 'abcd'.all_after_last('z') == 'abcd' pub fn (s string) all_after_last(sub string) string { - pos := s.last_index_(sub) + pos := s.index_last_(sub) if pos == -1 { return s.clone() } return s[pos + sub.len..] } -// all_after_first returns the contents after the first occurence of `sub` in the string. +// all_after_first returns the contents after the first occurrence of `sub` in the string. // If the substring is not found, it returns the full input string. // Example: assert '23:34:45.234'.all_after_first(':') == '34:45.234' // Example: assert 'abcd'.all_after_first('z') == 'abcd' @@ -1916,7 +2158,7 @@ pub fn (s string) all_after_first(sub string) string { return s[pos + sub.len..] } -// after returns the contents after the last occurence of `sub` in the string. +// after returns the contents after the last occurrence of `sub` in the string. // If the substring is not found, it returns the full input string. // Example: assert '23:34:45.234'.after(':') == '45.234' // Example: assert 'abcd'.after('z') == 'abcd' @@ -1926,7 +2168,7 @@ pub fn (s string) after(sub string) string { return s.all_after_last(sub) } -// after_char returns the contents after the first occurence of `sub` character in the string. +// after_char returns the contents after the first occurrence of `sub` character in the string. // If the substring is not found, it returns the full input string. // Example: assert '23:34:45.234'.after_char(`:`) == '34:45.234' // Example: assert 'abcd'.after_char(`:`) == 'abcd' @@ -2098,7 +2340,7 @@ pub fn (s string) fields() []string { } // strip_margin allows multi-line strings to be formatted in a way that removes white-space -// before a delimeter. by default `|` is used. +// before a delimiter. By default `|` is used. // Note: the delimiter has to be a byte at this time. That means surrounding // the value in ``. // @@ -2196,7 +2438,7 @@ pub fn (s string) trim_indent() string { .filter(!it.is_blank()) .map(it.indent_width()) - mut min_common_indent := int(2147483647) // max int + mut min_common_indent := int(max_int) // max int for line_indent in lines_indents { if line_indent < min_common_indent { min_common_indent = line_indent @@ -2205,12 +2447,12 @@ pub fn (s string) trim_indent() string { // trim first line if it's blank if lines.len > 0 && lines.first().is_blank() { - lines = lines[1..] + lines = unsafe { lines[1..] } } // trim last line if it's blank if lines.len > 0 && lines.last().is_blank() { - lines = lines[..lines.len - 1] + lines = unsafe { lines[..lines.len - 1] } } mut trimmed_lines := []string{cap: lines.len} From 0215931367dd1dd7d525415bd2d99d1c6287e0c6 Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Thu, 21 Mar 2024 07:35:12 +0100 Subject: [PATCH 3/6] test: update tests to run with `v test` (#46) --- .github/workflows/analyzer_tests.yml | 3 --- tests/{main.v => analyzer_test.v} | 5 +++-- tests/bench.v | 2 +- tests/completion.v | 2 +- tests/definitions.v | 2 +- tests/documentation.v | 2 +- tests/implementations.v | 2 +- tests/supers.v | 2 +- tests/{type_tests.v => types.v} | 2 +- 9 files changed, 10 insertions(+), 12 deletions(-) rename tests/{main.v => analyzer_test.v} (92%) rename tests/{type_tests.v => types.v} (98%) diff --git a/.github/workflows/analyzer_tests.yml b/.github/workflows/analyzer_tests.yml index e2082ec6..5db8a015 100644 --- a/.github/workflows/analyzer_tests.yml +++ b/.github/workflows/analyzer_tests.yml @@ -44,7 +44,4 @@ jobs: submodules: true - name: Run tests - run: cd tests && v run . - - - name: Run other V tests run: v test . diff --git a/tests/main.v b/tests/analyzer_test.v similarity index 92% rename from tests/main.v rename to tests/analyzer_test.v index 4e324ba1..82d38907 100644 --- a/tests/main.v +++ b/tests/analyzer_test.v @@ -1,15 +1,16 @@ -module main +module tests import os import term import testing -fn main() { +fn test_all() { defer { os.rmdir_all(testing.temp_path) or { println('Failed to remove temp path: ${testing.temp_path}') } } + os.chdir(os.join_path(@VMODROOT, 'tests'))! mut testers := []testing.Tester{} diff --git a/tests/bench.v b/tests/bench.v index 7297325f..e3b17502 100644 --- a/tests/bench.v +++ b/tests/bench.v @@ -1,4 +1,4 @@ -module main +module tests import testing diff --git a/tests/completion.v b/tests/completion.v index c3a01b17..4171aaeb 100644 --- a/tests/completion.v +++ b/tests/completion.v @@ -1,4 +1,4 @@ -module main +module tests import testing import server.completion.providers diff --git a/tests/definitions.v b/tests/definitions.v index 43e484da..d1bb6023 100644 --- a/tests/definitions.v +++ b/tests/definitions.v @@ -1,4 +1,4 @@ -module main +module tests import testing diff --git a/tests/documentation.v b/tests/documentation.v index 894852fd..9fd917e7 100644 --- a/tests/documentation.v +++ b/tests/documentation.v @@ -1,4 +1,4 @@ -module main +module tests import testing diff --git a/tests/implementations.v b/tests/implementations.v index bcf95f51..ba1df140 100644 --- a/tests/implementations.v +++ b/tests/implementations.v @@ -1,4 +1,4 @@ -module main +module tests import testing diff --git a/tests/supers.v b/tests/supers.v index 1fed39e5..4a9e58ef 100644 --- a/tests/supers.v +++ b/tests/supers.v @@ -1,4 +1,4 @@ -module main +module tests import testing diff --git a/tests/type_tests.v b/tests/types.v similarity index 98% rename from tests/type_tests.v rename to tests/types.v index e1988987..04dffeef 100644 --- a/tests/type_tests.v +++ b/tests/types.v @@ -1,4 +1,4 @@ -module main +module tests import testing From 93f97dfd8a08e55de169cb9a34ab9e2466fc0f5b Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Thu, 21 Mar 2024 07:36:09 +0100 Subject: [PATCH 4/6] ci: extend release workflow; automate assets uploads on tag creation (#39) --- .../{nightly_release.yml => release.yml} | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) rename .github/workflows/{nightly_release.yml => release.yml} (69%) diff --git a/.github/workflows/nightly_release.yml b/.github/workflows/release.yml similarity index 69% rename from .github/workflows/nightly_release.yml rename to .github/workflows/release.yml index 823d6770..500bb0b1 100644 --- a/.github/workflows/nightly_release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release Development Version +name: Release on: workflow_dispatch: @@ -12,7 +12,7 @@ on: - '**/*_test.v' - '**/*.md' - '.github/**' - - '!**/nightly_release.yml' + - '!**/release.yml' permissions: contents: write @@ -51,7 +51,10 @@ jobs: submodules: true - name: Compile - run: v run build.vsh debug + shell: bash + run: | + [ $GITHUB_REF_TYPE == tag ] && mode="release" || mode="debug" + v run build.vsh $mode - name: Upload artifact uses: actions/upload-artifact@v4 @@ -62,23 +65,36 @@ jobs: - name: Prepare release shell: bash run: | - now=$(date -u +'%Y-%m-%d %H:%M:%S UTC') - echo "BODY=Generated on $now from commit ${{ github.sha }}." >> $GITHUB_ENV 7z a -tzip ${{ env.ARTIFACT }}.zip ./bin/v-analyzer${{ matrix.bin_ext }} + if [ $GITHUB_REF_TYPE != tag ]; then + now=$(date -u +'%Y-%m-%d %H:%M:%S UTC') + echo "BODY=Generated on $now from commit ${{ github.sha }}." >> $GITHUB_ENV + fi - name: Update nightly tag + if: github.ref_type != 'tag' uses: richardsimko/update-tag@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: nightly - - name: Release + - name: Release development version + if: github.ref_type != 'tag' uses: ncipollo/release-action@v1 with: artifacts: ${{ env.ARTIFACT }}.zip tag: nightly body: ${{ env.BODY }} name: v-analyzer development build + allowUpdates: true prerelease: true + + - name: Release latest version + if: github.ref_type == 'tag' + uses: ncipollo/release-action@v1 + with: + artifacts: ${{ env.ARTIFACT }}.zip allowUpdates: true + omitBodyDuringUpdate: true + omitNameDuringUpdate: true From 366d30c2438ca396f2f8304d891ca22e7c14b922 Mon Sep 17 00:00:00 2001 From: Lycs-D <139197676+Lycs-D@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:36:35 +0800 Subject: [PATCH 5/6] [tree_sitter] Update tree-sitter-cli version to 0.22.2 (#41) --- tree_sitter_v/.gitattributes | 3 - tree_sitter_v/.gitignore | 8 + tree_sitter_v/bindings/v/bindings.h | 4 +- tree_sitter_v/package.json | 26 ++- tree_sitter_v/src/parser.c | 11 +- tree_sitter_v/src/tree_sitter/alloc.h | 54 +++++ tree_sitter_v/src/tree_sitter/array.h | 290 ++++++++++++++++++++++++++ v_tree_sitter/core | 2 +- 8 files changed, 385 insertions(+), 13 deletions(-) create mode 100644 tree_sitter_v/src/tree_sitter/alloc.h create mode 100644 tree_sitter_v/src/tree_sitter/array.h diff --git a/tree_sitter_v/.gitattributes b/tree_sitter_v/.gitattributes index 6a247231..c5f672fe 100644 --- a/tree_sitter_v/.gitattributes +++ b/tree_sitter_v/.gitattributes @@ -1,8 +1,5 @@ grammar.js symlink=file -bindings/node/* linguist-generated -bindings/rust/* linguist-generated - src/tree_sitter/* linguist-generated src/grammar.json linguist-generated src/node-types.json linguist-generated diff --git a/tree_sitter_v/.gitignore b/tree_sitter_v/.gitignore index b9073cae..11298a9d 100644 --- a/tree_sitter_v/.gitignore +++ b/tree_sitter_v/.gitignore @@ -3,6 +3,7 @@ node_modules/ *.log build/ package-lock.json +Package.swift # Ignore binary output folders bin/ @@ -19,6 +20,13 @@ yarn.lock Cargo.toml +bindings/c/ +bindings/go/ bindings/node/ +bindings/python/ bindings/rust/ +bindings/swift/ binding.gyp + +pyproject.toml +setup.py diff --git a/tree_sitter_v/bindings/v/bindings.h b/tree_sitter_v/bindings/v/bindings.h index 0ff801b5..c602e801 100644 --- a/tree_sitter_v/bindings/v/bindings.h +++ b/tree_sitter_v/bindings/v/bindings.h @@ -1,13 +1,13 @@ #ifndef TREE_SITTER_V_H_ #define TREE_SITTER_V_H_ -#include +typedef struct TSLanguage TSLanguage; #ifdef __cplusplus extern "C" { #endif -extern TSLanguage *tree_sitter_v(); +const TSLanguage *tree_sitter_v(void); #ifdef __cplusplus } diff --git a/tree_sitter_v/package.json b/tree_sitter_v/package.json index be8708fc..5871ea80 100644 --- a/tree_sitter_v/package.json +++ b/tree_sitter_v/package.json @@ -2,6 +2,7 @@ "name": "tree-sitter-v", "version": "0.0.4-beta.1", "main": "bindings/node", + "types": "bindings/node", "license": "MIT", "repository": { "type": "git", @@ -9,17 +10,29 @@ }, "scripts": { "test": "tree-sitter test", - "generate": "tree-sitter generate && v run generate_types.vsh", + "generate": "tree-sitter generate --no-bindings && v run generate_types.vsh", "parse": "tree-sitter parse", "parseg": "tree-sitter parse --debug-graph", + "install": "node-gyp-build", + "prebuildify": "prebuildify --napi --strip", "format": "prettier --write \"**/*.cjs\"" }, "dependencies": { - "nan": "^2.19.0" + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } }, "devDependencies": { "prettier": "^3.2.5", - "tree-sitter-cli": "^0.22.1" + "tree-sitter-cli": "^0.22.2", + "prebuildify": "^6.0.0" }, "tree-sitter": [ { @@ -30,5 +43,12 @@ "v.mod" ] } + ], + "files": [ + "grammar.js", + "prebuilds/**", + "bindings/node/*", + "queries/*", + "src/**" ] } diff --git a/tree_sitter_v/src/parser.c b/tree_sitter_v/src/parser.c index a5a05d95..2cd5ff64 100644 --- a/tree_sitter_v/src/parser.c +++ b/tree_sitter_v/src/parser.c @@ -1,7 +1,6 @@ #include "tree_sitter/parser.h" #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif @@ -309059,11 +309058,15 @@ static const TSParseActionEntry ts_parse_actions[] = { #ifdef __cplusplus extern "C" { #endif -#ifdef _WIN32 -#define extern __declspec(dllexport) +#ifdef TREE_SITTER_HIDE_SYMBOLS +#define TS_PUBLIC +#elif defined(_WIN32) +#define TS_PUBLIC __declspec(dllexport) +#else +#define TS_PUBLIC __attribute__((visibility("default"))) #endif -extern const TSLanguage *tree_sitter_v(void) { +TS_PUBLIC const TSLanguage *tree_sitter_v() { static const TSLanguage language = { .version = LANGUAGE_VERSION, .symbol_count = SYMBOL_COUNT, diff --git a/tree_sitter_v/src/tree_sitter/alloc.h b/tree_sitter_v/src/tree_sitter/alloc.h new file mode 100644 index 00000000..1f4466d7 --- /dev/null +++ b/tree_sitter_v/src/tree_sitter/alloc.h @@ -0,0 +1,54 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// Allow clients to override allocation functions +#ifdef TREE_SITTER_REUSE_ALLOCATOR + +extern void *(*ts_current_malloc)(size_t); +extern void *(*ts_current_calloc)(size_t, size_t); +extern void *(*ts_current_realloc)(void *, size_t); +extern void (*ts_current_free)(void *); + +#ifndef ts_malloc +#define ts_malloc ts_current_malloc +#endif +#ifndef ts_calloc +#define ts_calloc ts_current_calloc +#endif +#ifndef ts_realloc +#define ts_realloc ts_current_realloc +#endif +#ifndef ts_free +#define ts_free ts_current_free +#endif + +#else + +#ifndef ts_malloc +#define ts_malloc malloc +#endif +#ifndef ts_calloc +#define ts_calloc calloc +#endif +#ifndef ts_realloc +#define ts_realloc realloc +#endif +#ifndef ts_free +#define ts_free free +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ALLOC_H_ diff --git a/tree_sitter_v/src/tree_sitter/array.h b/tree_sitter_v/src/tree_sitter/array.h new file mode 100644 index 00000000..15a3b233 --- /dev/null +++ b/tree_sitter_v/src/tree_sitter/array.h @@ -0,0 +1,290 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./alloc.h" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4101) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#define Array(T) \ + struct { \ + T *contents; \ + uint32_t size; \ + uint32_t capacity; \ + } + +/// Initialize an array. +#define array_init(self) \ + ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) + +/// Create an empty array. +#define array_new() \ + { NULL, 0, 0 } + +/// Get a pointer to the element at a given `index` in the array. +#define array_get(self, _index) \ + (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) + +/// Get a pointer to the first element in the array. +#define array_front(self) array_get(self, 0) + +/// Get a pointer to the last element in the array. +#define array_back(self) array_get(self, (self)->size - 1) + +/// Clear the array, setting its size to zero. Note that this does not free any +/// memory allocated for the array's contents. +#define array_clear(self) ((self)->size = 0) + +/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is +/// less than the array's current capacity, this function has no effect. +#define array_reserve(self, new_capacity) \ + _array__reserve((Array *)(self), array_elem_size(self), new_capacity) + +/// Free any memory allocated for this array. Note that this does not free any +/// memory allocated for the array's contents. +#define array_delete(self) _array__delete((Array *)(self)) + +/// Push a new `element` onto the end of the array. +#define array_push(self, element) \ + (_array__grow((Array *)(self), 1, array_elem_size(self)), \ + (self)->contents[(self)->size++] = (element)) + +/// Increase the array's size by `count` elements. +/// New elements are zero-initialized. +#define array_grow_by(self, count) \ + do { \ + if ((count) == 0) break; \ + _array__grow((Array *)(self), count, array_elem_size(self)); \ + memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ + (self)->size += (count); \ + } while (0) + +/// Append all elements from one array to the end of another. +#define array_push_all(self, other) \ + array_extend((self), (other)->size, (other)->contents) + +/// Append `count` elements to the end of the array, reading their values from the +/// `contents` pointer. +#define array_extend(self, count, contents) \ + _array__splice( \ + (Array *)(self), array_elem_size(self), (self)->size, \ + 0, count, contents \ + ) + +/// Remove `old_count` elements from the array starting at the given `index`. At +/// the same index, insert `new_count` new elements, reading their values from the +/// `new_contents` pointer. +#define array_splice(self, _index, old_count, new_count, new_contents) \ + _array__splice( \ + (Array *)(self), array_elem_size(self), _index, \ + old_count, new_count, new_contents \ + ) + +/// Insert one `element` into the array at the given `index`. +#define array_insert(self, _index, element) \ + _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element)) + +/// Remove one element from the array at the given `index`. +#define array_erase(self, _index) \ + _array__erase((Array *)(self), array_elem_size(self), _index) + +/// Pop the last element off the array, returning the element by value. +#define array_pop(self) ((self)->contents[--(self)->size]) + +/// Assign the contents of one array to another, reallocating if necessary. +#define array_assign(self, other) \ + _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self)) + +/// Swap one array with another +#define array_swap(self, other) \ + _array__swap((Array *)(self), (Array *)(other)) + +/// Get the size of the array contents +#define array_elem_size(self) (sizeof *(self)->contents) + +/// Search a sorted array for a given `needle` value, using the given `compare` +/// callback to determine the order. +/// +/// If an existing element is found to be equal to `needle`, then the `index` +/// out-parameter is set to the existing value's index, and the `exists` +/// out-parameter is set to true. Otherwise, `index` is set to an index where +/// `needle` should be inserted in order to preserve the sorting, and `exists` +/// is set to false. +#define array_search_sorted_with(self, compare, needle, _index, _exists) \ + _array__search_sorted(self, 0, compare, , needle, _index, _exists) + +/// Search a sorted array for a given `needle` value, using integer comparisons +/// of a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_with`. +#define array_search_sorted_by(self, field, needle, _index, _exists) \ + _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) + +/// Insert a given `value` into a sorted array, using the given `compare` +/// callback to determine the order. +#define array_insert_sorted_with(self, compare, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +/// Insert a given `value` into a sorted array, using integer comparisons of +/// a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_by`. +#define array_insert_sorted_by(self, field, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +// Private + +typedef Array(void) Array; + +/// This is not what you're looking for, see `array_delete`. +static inline void _array__delete(Array *self) { + if (self->contents) { + ts_free(self->contents); + self->contents = NULL; + self->size = 0; + self->capacity = 0; + } +} + +/// This is not what you're looking for, see `array_erase`. +static inline void _array__erase(Array *self, size_t element_size, + uint32_t index) { + assert(index < self->size); + char *contents = (char *)self->contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (self->size - index - 1) * element_size); + self->size--; +} + +/// This is not what you're looking for, see `array_reserve`. +static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) { + if (new_capacity > self->capacity) { + if (self->contents) { + self->contents = ts_realloc(self->contents, new_capacity * element_size); + } else { + self->contents = ts_malloc(new_capacity * element_size); + } + self->capacity = new_capacity; + } +} + +/// This is not what you're looking for, see `array_assign`. +static inline void _array__assign(Array *self, const Array *other, size_t element_size) { + _array__reserve(self, element_size, other->size); + self->size = other->size; + memcpy(self->contents, other->contents, self->size * element_size); +} + +/// This is not what you're looking for, see `array_swap`. +static inline void _array__swap(Array *self, Array *other) { + Array swap = *other; + *other = *self; + *self = swap; +} + +/// This is not what you're looking for, see `array_push` or `array_grow_by`. +static inline void _array__grow(Array *self, uint32_t count, size_t element_size) { + uint32_t new_size = self->size + count; + if (new_size > self->capacity) { + uint32_t new_capacity = self->capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + _array__reserve(self, element_size, new_capacity); + } +} + +/// This is not what you're looking for, see `array_splice`. +static inline void _array__splice(Array *self, size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = self->size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= self->size); + + _array__reserve(self, element_size, new_size); + + char *contents = (char *)self->contents; + if (self->size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (self->size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } + self->size += new_count - old_count; +} + +/// A binary search routine, based on Rust's `std::slice::binary_search_by`. +/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. +#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ + do { \ + *(_index) = start; \ + *(_exists) = false; \ + uint32_t size = (self)->size - *(_index); \ + if (size == 0) break; \ + int comparison; \ + while (size > 1) { \ + uint32_t half_size = size / 2; \ + uint32_t mid_index = *(_index) + half_size; \ + comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ + if (comparison <= 0) *(_index) = mid_index; \ + size -= half_size; \ + } \ + comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ + if (comparison == 0) *(_exists) = true; \ + else if (comparison < 0) *(_index) += 1; \ + } while (0) + +/// Helper macro for the `_sorted_by` routines below. This takes the left (existing) +/// parameter by reference in order to work with the generic sorting function above. +#define _compare_int(a, b) ((int)*(a) - (int)(b)) + +#ifdef _MSC_VER +#pragma warning(default : 4101) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ARRAY_H_ diff --git a/v_tree_sitter/core b/v_tree_sitter/core index 30fd71f5..fc15f621 160000 --- a/v_tree_sitter/core +++ b/v_tree_sitter/core @@ -1 +1 @@ -Subproject commit 30fd71f5acbb8979b980e8df15cabd2ba5b70cca +Subproject commit fc15f621334a262039ffaded5937e2844f88da61 From 3439620ad96aa540f8dc454a332bc2d59314178f Mon Sep 17 00:00:00 2001 From: Lycs-D <139197676+Lycs-D@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:44:23 +0800 Subject: [PATCH 6/6] [analyzer] fix anonymous functions are self-invoking type mismatch (#48) --- analyzer/psi/TypeInferer.v | 14 +++++++++----- tests/testdata/types/function_literal.v | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/analyzer/psi/TypeInferer.v b/analyzer/psi/TypeInferer.v index 720a9ee8..94196c47 100644 --- a/analyzer/psi/TypeInferer.v +++ b/analyzer/psi/TypeInferer.v @@ -27,6 +27,9 @@ pub fn (t &TypeInferer) infer_type(elem ?PsiElement) types.Type { pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type { element := elem or { return types.unknown_type } + + mut visited := map[string]types.Type{} + if element.node.type_name in [ .in_expression, .not_in_expression, @@ -179,7 +182,6 @@ pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type { if element is TypeInitializer { type_element := element.find_child_by_type(.plain_type) or { return types.unknown_type } - mut visited := map[string]types.Type{} return t.convert_type(type_element, mut visited) } @@ -252,6 +254,12 @@ pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type { } if element is CallExpression { + if grand := element.expression() { + if grand is FunctionLiteral { + signature := grand.signature() or { return types.unknown_type } + return t.convert_type(signature.result(), mut visited) + } + } return t.infer_call_expr_type(element) } @@ -347,7 +355,6 @@ pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type { } if element is TypeReferenceExpression { - mut visited := map[string]types.Type{} return t.infer_type_reference_type(element, mut visited) } @@ -355,13 +362,10 @@ pub fn (t &TypeInferer) infer_type_impl(elem ?PsiElement) types.Type { type_element := element.find_child_by_type_or_stub(.plain_type) or { return types.unknown_type } - mut visited := map[string]types.Type{} return t.convert_type(type_element, mut visited) } if element is EmbeddedDefinition { - mut visited := map[string]types.Type{} - if qualified_type := element.find_child_by_type_or_stub(.qualified_type) { return t.convert_type_inner(qualified_type, mut visited) } diff --git a/tests/testdata/types/function_literal.v b/tests/testdata/types/function_literal.v index a6b99cdc..c5d7ccf8 100644 --- a/tests/testdata/types/function_literal.v +++ b/tests/testdata/types/function_literal.v @@ -29,4 +29,7 @@ fn calls() { func1 := fn (i int) int {} expr_type(func1(), 'int') + + func2 := fn (i int) int {}() + expr_type(func2, 'int') }