checker: check assigning immutable reference struct field (fix #20814) (#20883)

This commit is contained in:
yuyi 2024-02-23 01:22:13 +08:00 committed by GitHub
parent 1d80cb9157
commit 667f65bb5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 138 additions and 86 deletions

View file

@ -131,7 +131,7 @@ fn json(file string) string {
pref: pref_ pref: pref_
} }
// parse file with comment // parse file with comment
ast_file := parser.parse_file(file, t.table, .parse_comments, t.pref) ast_file := parser.parse_file(file, mut t.table, .parse_comments, t.pref)
t.root = t.ast_file(ast_file) t.root = t.ast_file(ast_file)
// generate the ast string // generate the ast string
s := json_print(t.root) s := json_print(t.root)
@ -140,10 +140,10 @@ fn json(file string) string {
// the ast tree // the ast tree
struct Tree { struct Tree {
table &ast.Table = unsafe { nil } pref &pref.Preferences = unsafe { nil }
pref &pref.Preferences = unsafe { nil }
mut: mut:
root Node // the root of tree table &ast.Table = unsafe { nil }
root Node // the root of tree
} }
// tree node // tree node

View file

@ -160,10 +160,10 @@ fn (foptions &FormatOptions) vlog(msg string) {
fn (foptions &FormatOptions) format_file(file string) { fn (foptions &FormatOptions) format_file(file string) {
foptions.vlog('vfmt2 running fmt.fmt over file: ${file}') foptions.vlog('vfmt2 running fmt.fmt over file: ${file}')
prefs, table := setup_preferences_and_table() prefs, mut table := setup_preferences_and_table()
file_ast := parser.parse_file(file, table, .parse_comments, prefs) file_ast := parser.parse_file(file, mut table, .parse_comments, prefs)
// checker.new_checker(table, prefs).check(file_ast) // checker.new_checker(table, prefs).check(file_ast)
formatted_content := fmt.fmt(file_ast, table, prefs, foptions.is_debug) formatted_content := fmt.fmt(file_ast, mut table, prefs, foptions.is_debug)
file_name := os.file_name(file) file_name := os.file_name(file)
ulid := rand.ulid() ulid := rand.ulid()
vfmt_output_path := os.join_path(vtmp_folder, 'vfmt_${ulid}_${file_name}') vfmt_output_path := os.join_path(vtmp_folder, 'vfmt_${ulid}_${file_name}')
@ -174,11 +174,13 @@ fn (foptions &FormatOptions) format_file(file string) {
fn (foptions &FormatOptions) format_pipe() { fn (foptions &FormatOptions) format_pipe() {
foptions.vlog('vfmt2 running fmt.fmt over stdin') foptions.vlog('vfmt2 running fmt.fmt over stdin')
prefs, table := setup_preferences_and_table() prefs, mut table := setup_preferences_and_table()
input_text := os.get_raw_lines_joined() input_text := os.get_raw_lines_joined()
file_ast := parser.parse_text(input_text, '', table, .parse_comments, prefs) file_ast := parser.parse_text(input_text, '', mut table, .parse_comments, prefs)
// checker.new_checker(table, prefs).check(file_ast) // checker.new_checker(table, prefs).check(file_ast)
formatted_content := fmt.fmt(file_ast, table, prefs, foptions.is_debug, source_text: input_text) formatted_content := fmt.fmt(file_ast, mut table, prefs, foptions.is_debug,
source_text: input_text
)
print(formatted_content) print(formatted_content)
flush_stdout() flush_stdout()
foptions.vlog('fmt.fmt worked and ${formatted_content.len} bytes were written to stdout.') foptions.vlog('fmt.fmt worked and ${formatted_content.len} bytes were written to stdout.')

View file

@ -56,7 +56,8 @@ fn main() {
time.sleep(ms * time.millisecond) time.sleep(ms * time.millisecond)
exit(ecode_timeout) exit(ecode_timeout)
}(context.timeout_ms) }(context.timeout_ms)
_ := parser.parse_text(source, context.path, context.table, .skip_comments, context.pref) _ := parser.parse_text(source, context.path, mut context.table, .skip_comments,
context.pref)
context.log('> worker ${pid:5} finished parsing ${context.path}') context.log('> worker ${pid:5} finished parsing ${context.path}')
exit(0) exit(0)
} else { } else {

View file

@ -103,9 +103,9 @@ fn (mut vt Vet) vet_file(path string) {
mut prefs := pref.new_preferences() mut prefs := pref.new_preferences()
prefs.is_vet = true prefs.is_vet = true
prefs.is_vsh = path.ends_with('.vsh') prefs.is_vsh = path.ends_with('.vsh')
table := ast.new_table() mut table := ast.new_table()
vt.vprintln("vetting file '${path}'...") vt.vprintln("vetting file '${path}'...")
_, errors, notices := parser.parse_vet_file(path, table, prefs) _, errors, notices := parser.parse_vet_file(path, mut table, prefs)
// Transfer errors from scanner and parser // Transfer errors from scanner and parser
vt.errors << errors vt.errors << errors
vt.notices << notices vt.notices << notices

View file

@ -4943,7 +4943,7 @@ struct MyStruct {
} }
fn main() { fn main() {
m := MyStruct{} mut m := MyStruct{}
mut r := RefStruct{ mut r := RefStruct{
r: &m r: &m
} }
@ -5046,7 +5046,7 @@ fn use_stack() {
} }
fn main() { fn main() {
m := MyStruct{} mut m := MyStruct{}
mut r := RefStruct{ mut r := RefStruct{
r: &m r: &m
} }

View file

@ -35,9 +35,9 @@ fn test_type_size() {
mut b := builder.new_builder(pref_) mut b := builder.new_builder(pref_)
mut files := b.get_builtin_files() mut files := b.get_builtin_files()
b.set_module_lookup_paths() b.set_module_lookup_paths()
parser.parse_files(files, b.table, b.pref) parser.parse_files(files, mut b.table, b.pref)
b.parse_imports() b.parse_imports()
parser.parse_file(@FILE, b.table, .parse_comments, b.pref) parser.parse_file(@FILE, mut b.table, .parse_comments, b.pref)
mut t := b.table mut t := b.table

View file

@ -4,9 +4,9 @@ import v.parser
import v.pref import v.pref
fn parse_text(text string) &ast.File { fn parse_text(text string) &ast.File {
tbl := ast.new_table() mut tbl := ast.new_table()
prefs := pref.new_preferences() prefs := pref.new_preferences()
return parser.parse_text(text, '', tbl, .skip_comments, prefs) return parser.parse_text(text, '', mut tbl, .skip_comments, prefs)
} }
struct NodeByOffset { struct NodeByOffset {

View file

@ -89,8 +89,8 @@ pub fn new_builder(pref_ &pref.Preferences) Builder {
} }
pub fn (mut b Builder) interpret_text(code string, v_files []string) ! { pub fn (mut b Builder) interpret_text(code string, v_files []string) ! {
b.parsed_files = parser.parse_files(v_files, b.table, b.pref) b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref)
b.parsed_files << parser.parse_text(code, '', b.table, .skip_comments, b.pref) b.parsed_files << parser.parse_text(code, '', mut b.table, .skip_comments, b.pref)
b.parse_imports() b.parse_imports()
if b.pref.only_check_syntax { if b.pref.only_check_syntax {
@ -109,7 +109,7 @@ pub fn (mut b Builder) front_stages(v_files []string) ! {
util.timing_start('PARSE') util.timing_start('PARSE')
util.timing_start('Builder.front_stages.parse_files') util.timing_start('Builder.front_stages.parse_files')
b.parsed_files = parser.parse_files(v_files, b.table, b.pref) b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref)
timers.show('Builder.front_stages.parse_files') timers.show('Builder.front_stages.parse_files')
b.parse_imports() b.parse_imports()
@ -144,7 +144,7 @@ pub fn (mut b Builder) middle_stages() ! {
// //
b.table.complete_interface_check() b.table.complete_interface_check()
if b.pref.skip_unused { if b.pref.skip_unused {
markused.mark_used(mut b.table, b.pref, b.parsed_files) markused.mark_used(mut b.table, mut b.pref, b.parsed_files)
} }
if b.pref.show_callgraph { if b.pref.show_callgraph {
callgraph.show(mut b.table, b.pref, b.parsed_files) callgraph.show(mut b.table, b.pref, b.parsed_files)
@ -222,7 +222,7 @@ pub fn (mut b Builder) parse_imports() {
} }
// eprintln('>> ast_file.path: $ast_file.path , done: $done_imports, `import $mod` => $v_files') // eprintln('>> ast_file.path: $ast_file.path , done: $done_imports, `import $mod` => $v_files')
// Add all imports referenced by these libs // Add all imports referenced by these libs
parsed_files := parser.parse_files(v_files, b.table, b.pref) parsed_files := parser.parse_files(v_files, mut b.table, b.pref)
for file in parsed_files { for file in parsed_files {
mut name := file.mod.name mut name := file.mod.name
if name == '' { if name == '' {

View file

@ -73,7 +73,7 @@ pub fn gen_c(mut b builder.Builder, v_files []string) string {
} }
util.timing_start('C GEN') util.timing_start('C GEN')
header, res, out_str, out_fn_start_pos := c.gen(b.parsed_files, b.table, b.pref) header, res, out_str, out_fn_start_pos := c.gen(b.parsed_files, mut b.table, b.pref)
util.timing_measure('C GEN') util.timing_measure('C GEN')
if b.pref.parallel_cc { if b.pref.parallel_cc {

View file

@ -36,6 +36,6 @@ pub fn build_golang(mut b builder.Builder, v_files []string, out_file string) {
} }
b.front_and_middle_stages(nvf) or { return } b.front_and_middle_stages(nvf) or { return }
util.timing_start('Golang GEN') util.timing_start('Golang GEN')
b.stats_lines, b.stats_bytes = golang.gen(b.parsed_files, b.table, out_file, b.pref) b.stats_lines, b.stats_bytes = golang.gen(b.parsed_files, mut b.table, out_file, b.pref)
util.timing_measure('Golang GEN') util.timing_measure('Golang GEN')
} }

View file

@ -41,7 +41,7 @@ pub fn build_js(mut b builder.Builder, v_files []string, out_file string) {
pub fn gen_js(mut b builder.Builder, v_files []string) string { pub fn gen_js(mut b builder.Builder, v_files []string) string {
b.front_and_middle_stages(v_files) or { return '' } b.front_and_middle_stages(v_files) or { return '' }
util.timing_start('JS GEN') util.timing_start('JS GEN')
res := js.gen(b.parsed_files, b.table, b.pref) res := js.gen(b.parsed_files, mut b.table, b.pref)
util.timing_measure('JS GEN') util.timing_measure('JS GEN')
return res return res
} }

View file

@ -58,6 +58,6 @@ pub fn build_native(mut b builder.Builder, v_files []string, out_file string) {
eprintln('Error: Only arm64 and amd64 are supported by V') eprintln('Error: Only arm64 and amd64 are supported by V')
} }
} }
b.stats_lines, b.stats_bytes = native.gen(b.parsed_files, b.table, out_file, b.pref) b.stats_lines, b.stats_bytes = native.gen(b.parsed_files, mut b.table, out_file, b.pref)
util.timing_measure('Native GEN') util.timing_measure('Native GEN')
} }

View file

@ -31,6 +31,6 @@ pub fn compile_wasm(mut b builder.Builder) {
pub fn build_wasm(mut b builder.Builder, v_files []string, out_file string) { pub fn build_wasm(mut b builder.Builder, v_files []string, out_file string) {
b.front_and_middle_stages(v_files) or { return } b.front_and_middle_stages(v_files) or { return }
util.timing_start('WebAssembly GEN') util.timing_start('WebAssembly GEN')
wasm.gen(b.parsed_files, b.table, out_file, b.pref) wasm.gen(b.parsed_files, mut b.table, out_file, b.pref)
util.timing_measure('WebAssembly GEN') util.timing_measure('WebAssembly GEN')
} }

View file

@ -223,11 +223,31 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) {
} }
} }
} }
if mut left is ast.Ident && mut right is ast.Ident { if left is ast.Ident && left.is_mut() && !c.inside_unsafe {
if !c.inside_unsafe && left_type.is_ptr() && left.is_mut() && right_type.is_ptr() if left_type.is_ptr() && mut right is ast.Ident && !right.is_mut()
&& !right.is_mut() { && right_type.is_ptr() {
c.error('`${right.name}` is immutable, cannot have a mutable reference to an immutable object', c.error('`${right.name}` is immutable, cannot have a mutable reference to an immutable object',
right.pos) right.pos)
} else if mut right is ast.StructInit {
typ_sym := c.table.sym(right.typ)
for init_field in right.init_fields {
if field_info := c.table.find_field_with_embeds(typ_sym, init_field.name) {
if field_info.is_mut {
if init_field.expr is ast.Ident && !init_field.expr.is_mut()
&& init_field.typ.is_ptr() {
c.note('`${init_field.expr.name}` is immutable, cannot have a mutable reference to an immutable object',
init_field.pos)
} else if init_field.expr is ast.PrefixExpr {
if init_field.expr.op == .amp
&& init_field.expr.right is ast.Ident
&& !init_field.expr.right.is_mut() {
c.note('`${init_field.expr.right.name}` is immutable, cannot have a mutable reference to an immutable object',
init_field.expr.right.pos)
}
}
}
}
}
} }
} }
} else { } else {

View file

@ -0,0 +1,7 @@
vlib/v/checker/tests/assign_immutable_reference_struct_field_err.vv:13:29: notice: `parent` is immutable, cannot have a mutable reference to an immutable object
11 | }
12 | // taking a reference of `parent` and putting it under a `mut:` struct field
13 | mut child := Tree{parent: &parent}
| ~~~~~~
14 |
15 | // unwrap the reference of `parent`, but declare it as `mut`

View file

@ -0,0 +1,19 @@
struct Tree {
mut:
garbage int
parent ?&Tree
}
fn main() {
// `parent` is not declared as mutable!
parent := Tree{
garbage: 11
}
// taking a reference of `parent` and putting it under a `mut:` struct field
mut child := Tree{parent: &parent}
// unwrap the reference of `parent`, but declare it as `mut`
mut inner_parent := child.parent?
inner_parent.garbage = 777 // !we can mutate `parent` freely!
println(parent.garbage) // 777
}

View file

@ -453,7 +453,7 @@ pub fn (mut d Doc) generate() ! {
if i == 0 { if i == 0 {
d.parent_mod_name = get_parent_mod(d.base_path) or { '' } d.parent_mod_name = get_parent_mod(d.base_path) or { '' }
} }
file_asts << parser.parse_file(file_path, d.table, comments_mode, d.prefs) file_asts << parser.parse_file(file_path, mut d.table, comments_mode, d.prefs)
} }
return d.file_asts(mut file_asts) return d.file_asts(mut file_asts)
} }

View file

@ -42,8 +42,8 @@ fn get_parent_mod(input_dir string) !string {
} }
return error('No V files found.') return error('No V files found.')
} }
tbl := ast.new_table() mut tbl := ast.new_table()
file_ast := parser.parse_file(v_files[0], tbl, .skip_comments, prefs) file_ast := parser.parse_file(v_files[0], mut tbl, .skip_comments, prefs)
if file_ast.mod.name == 'main' { if file_ast.mod.name == 'main' {
return '' return ''
} }

View file

@ -16,10 +16,10 @@ const max_len = [0, 35, 60, 85, 93, 100]
@[minify] @[minify]
pub struct Fmt { pub struct Fmt {
pref &pref.Preferences = unsafe { nil }
pub mut: pub mut:
file ast.File file ast.File
table &ast.Table = unsafe { nil } table &ast.Table = unsafe { nil }
pref &pref.Preferences = unsafe { nil }
is_debug bool is_debug bool
out strings.Builder out strings.Builder
out_imports strings.Builder out_imports strings.Builder
@ -62,7 +62,7 @@ pub struct FmtOptions {
source_text string source_text string
} }
pub fn fmt(file ast.File, table &ast.Table, pref_ &pref.Preferences, is_debug bool, options FmtOptions) string { pub fn fmt(file ast.File, mut table ast.Table, pref_ &pref.Preferences, is_debug bool, options FmtOptions) string {
mut f := Fmt{ mut f := Fmt{
file: file file: file
table: table table: table

View file

@ -26,9 +26,9 @@ fn test_bin2v_formatting() {
} }
prepare_bin2v_file() prepare_bin2v_file()
table := ast.new_table() mut table := ast.new_table()
file_ast := parser.parse_file(b2v_keep_path, table, .parse_comments, fpref) file_ast := parser.parse_file(b2v_keep_path, mut table, .parse_comments, fpref)
result_ocontent := fmt.fmt(file_ast, table, fpref, false) result_ocontent := fmt.fmt(file_ast, mut table, fpref, false)
eprintln('> the file ${b2v_keep_path} can be formatted.') eprintln('> the file ${b2v_keep_path} can be formatted.')
expected_ocontent := os.read_file(b2v_keep_path)! expected_ocontent := os.read_file(b2v_keep_path)!
if expected_ocontent != result_ocontent { if expected_ocontent != result_ocontent {

View file

@ -50,9 +50,9 @@ fn test_fmt() {
eprintln(fmt_bench.step_message_fail('cannot read from ${vrelpath}')) eprintln(fmt_bench.step_message_fail('cannot read from ${vrelpath}'))
continue continue
} }
table := ast.new_table() mut table := ast.new_table()
file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) file_ast := parser.parse_file(ipath, mut table, .parse_comments, fpref)
result_ocontent := fmt.fmt(file_ast, table, fpref, false) result_ocontent := fmt.fmt(file_ast, mut table, fpref, false)
if expected_ocontent != result_ocontent { if expected_ocontent != result_ocontent {
fmt_bench.fail() fmt_bench.fail()
eprintln(fmt_bench.step_message_fail('file ${vrelpath} after formatting, does not look as expected.')) eprintln(fmt_bench.step_message_fail('file ${vrelpath} after formatting, does not look as expected.'))

View file

@ -46,9 +46,9 @@ fn test_fmt() {
eprintln(fmt_bench.step_message_fail('cannot read from ${opath}')) eprintln(fmt_bench.step_message_fail('cannot read from ${opath}'))
continue continue
} }
table := ast.new_table() mut table := ast.new_table()
file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) file_ast := parser.parse_file(ipath, mut table, .parse_comments, fpref)
result_ocontent := fmt.fmt(file_ast, table, fpref, false) result_ocontent := fmt.fmt(file_ast, mut table, fpref, false)
if expected_ocontent != result_ocontent { if expected_ocontent != result_ocontent {
fmt_bench.fail() fmt_bench.fail()
eprintln(fmt_bench.step_message_fail('file ${ipath} after formatting, does not look as expected.')) eprintln(fmt_bench.step_message_fail('file ${ipath} after formatting, does not look as expected.'))

View file

@ -44,9 +44,9 @@ fn test_vlib_fmt() {
eprintln(fmt_bench.step_message_fail('cannot read from ${opath}')) eprintln(fmt_bench.step_message_fail('cannot read from ${opath}'))
continue continue
} }
table := ast.new_table() mut table := ast.new_table()
file_ast := parser.parse_file(ipath, table, .parse_comments, fpref) file_ast := parser.parse_file(ipath, mut table, .parse_comments, fpref)
result_ocontent := fmt.fmt(file_ast, table, fpref, false) result_ocontent := fmt.fmt(file_ast, mut table, fpref, false)
if expected_ocontent != result_ocontent { if expected_ocontent != result_ocontent {
fmt_bench.fail() fmt_bench.fail()
eprintln(fmt_bench.step_message_fail('file ${ipath} after formatting, does not look as expected.')) eprintln(fmt_bench.step_message_fail('file ${ipath} after formatting, does not look as expected.'))

View file

@ -261,7 +261,7 @@ struct GlobalConstDef {
is_precomputed bool // can be declared as a const in C: primitive, and a simple definition is_precomputed bool // can be declared as a const in C: primitive, and a simple definition
} }
pub fn gen(files []&ast.File, table &ast.Table, pref_ &pref.Preferences) (string, string, string, []int) { pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (string, string, string, []int) {
mut module_built := '' mut module_built := ''
if pref_.build_mode == .build_module { if pref_.build_mode == .build_module {
for file in files { for file in files {
@ -313,7 +313,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref_ &pref.Preferences) (string
module_built: module_built module_built: module_built
timers_should_print: timers_should_print timers_should_print: timers_should_print
timers: util.new_timers(should_print: timers_should_print, label: 'global_cgen') timers: util.new_timers(should_print: timers_should_print, label: 'global_cgen')
inner_loop: &ast.empty_stmt inner_loop: unsafe { &ast.empty_stmt }
field_data_type: ast.Type(table.find_type_idx('FieldData')) field_data_type: ast.Type(table.find_type_idx('FieldData'))
enum_data_type: ast.Type(table.find_type_idx('EnumData')) enum_data_type: ast.Type(table.find_type_idx('EnumData'))
is_cc_msvc: pref_.ccompiler == 'msvc' is_cc_msvc: pref_.ccompiler == 'msvc'

View file

@ -12,9 +12,9 @@ import os
const bs = '\\' const bs = '\\'
pub struct Gen { pub struct Gen {
pref &pref.Preferences = unsafe { nil }
pub mut: pub mut:
table &ast.Table = unsafe { nil } table &ast.Table = unsafe { nil }
pref &pref.Preferences = unsafe { nil }
// is_debug bool // is_debug bool
out strings.Builder out strings.Builder
out_imports strings.Builder out_imports strings.Builder
@ -45,7 +45,7 @@ pub mut:
nlines int nlines int
} }
pub fn gen(files []&ast.File, table &ast.Table, out_file string, pref_ &pref.Preferences) (int, int) { pub fn gen(files []&ast.File, mut table ast.Table, out_file string, pref_ &pref.Preferences) (int, int) {
mut g := Gen{ mut g := Gen{
table: table table: table
pref: pref_ pref: pref_

View file

@ -97,7 +97,7 @@ fn (mut g JsGen) write_tests_definitions() {
g.definitions.writeln('globalThis.g_test_fails = 0;') g.definitions.writeln('globalThis.g_test_fails = 0;')
} }
pub fn gen(files []&ast.File, table &ast.Table, pref_ &pref.Preferences) string { pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) string {
mut g := &JsGen{ mut g := &JsGen{
definitions: strings.new_builder(100) definitions: strings.new_builder(100)
table: table table: table

View file

@ -327,7 +327,7 @@ fn get_backend(arch pref.Arch, target_os pref.OS) !CodeGen {
return error('unsupported architecture') return error('unsupported architecture')
} }
pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref_ &pref.Preferences) (int, int) { pub fn gen(files []&ast.File, mut table ast.Table, out_name string, pref_ &pref.Preferences) (int, int) {
exe_name := if pref_.os == .windows && !out_name.ends_with('.exe') { exe_name := if pref_.os == .windows && !out_name.ends_with('.exe') {
out_name + '.exe' out_name + '.exe'
} else { } else {

View file

@ -1283,7 +1283,7 @@ pub fn (mut g Gen) calculate_enum_fields() {
} }
} }
pub fn gen(files []&ast.File, table &ast.Table, out_name string, w_pref &pref.Preferences) { pub fn gen(files []&ast.File, mut table ast.Table, out_name string, w_pref &pref.Preferences) {
stack_top := w_pref.wasm_stack_top stack_top := w_pref.wasm_stack_top
mut g := &Gen{ mut g := &Gen{
table: table table: table

View file

@ -7,7 +7,7 @@ import v.util
import v.pref import v.pref
// mark_used walks the AST, starting at main() and marks all used fns transitively // mark_used walks the AST, starting at main() and marks all used fns transitively
pub fn mark_used(mut table ast.Table, pref_ &pref.Preferences, ast_files []&ast.File) { pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&ast.File) {
mut all_fns, all_consts, all_globals := all_fn_const_and_global(ast_files) mut all_fns, all_consts, all_globals := all_fn_const_and_global(ast_files)
util.timing_start(@METHOD) util.timing_start(@METHOD)
defer { defer {

View file

@ -282,7 +282,7 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall {
// the tmpl inherits all parent scopes. previous functionality was just to // the tmpl inherits all parent scopes. previous functionality was just to
// inherit the scope from which the comptime call was made and no parents. // inherit the scope from which the comptime call was made and no parents.
// this is much simpler and allows access to globals. can be changed if needed. // this is much simpler and allows access to globals. can be changed if needed.
mut file := parse_comptime(tmpl_path, v_code, p.table, p.pref, p.scope) mut file := parse_comptime(tmpl_path, v_code, mut p.table, p.pref, mut p.scope)
file.path = tmpl_path file.path = tmpl_path
return ast.ComptimeCall{ return ast.ComptimeCall{
scope: unsafe { nil } scope: unsafe { nil }

View file

@ -116,7 +116,7 @@ pub mut:
__global codegen_files = unsafe { []&ast.File{} } __global codegen_files = unsafe { []&ast.File{} }
// for tests // for tests
pub fn parse_stmt(text string, table &ast.Table, scope &ast.Scope) ast.Stmt { pub fn parse_stmt(text string, mut table ast.Table, mut scope ast.Scope) ast.Stmt {
$if trace_parse_stmt ? { $if trace_parse_stmt ? {
eprintln('> ${@MOD}.${@FN} text: ${text}') eprintln('> ${@MOD}.${@FN} text: ${text}')
} }
@ -136,7 +136,7 @@ pub fn parse_stmt(text string, table &ast.Table, scope &ast.Scope) ast.Stmt {
return p.stmt(false) return p.stmt(false)
} }
pub fn parse_comptime(tmpl_path string, text string, table &ast.Table, pref_ &pref.Preferences, scope &ast.Scope) &ast.File { pub fn parse_comptime(tmpl_path string, text string, mut table ast.Table, pref_ &pref.Preferences, mut scope ast.Scope) &ast.File {
$if trace_parse_comptime ? { $if trace_parse_comptime ? {
eprintln('> ${@MOD}.${@FN} text: ${text}') eprintln('> ${@MOD}.${@FN} text: ${text}')
} }
@ -154,7 +154,7 @@ pub fn parse_comptime(tmpl_path string, text string, table &ast.Table, pref_ &pr
return res return res
} }
pub fn parse_text(text string, path string, table &ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File { pub fn parse_text(text string, path string, mut table ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File {
$if trace_parse_text ? { $if trace_parse_text ? {
eprintln('> ${@MOD}.${@FN} comments_mode: ${comments_mode:-20} | path: ${path:-20} | text: ${text}') eprintln('> ${@MOD}.${@FN} comments_mode: ${comments_mode:-20} | path: ${path:-20} | text: ${text}')
} }
@ -232,7 +232,7 @@ pub fn (mut p Parser) set_path(path string) {
} }
} }
pub fn parse_file(path string, table &ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File { pub fn parse_file(path string, mut table ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File {
// Note: when comments_mode == .toplevel_comments, // Note: when comments_mode == .toplevel_comments,
// the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip // the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip
// all the tricky inner comments. This is needed because we do not have a good general solution // all the tricky inner comments. This is needed because we do not have a good general solution
@ -258,7 +258,7 @@ pub fn parse_file(path string, table &ast.Table, comments_mode scanner.CommentsM
return res return res
} }
pub fn parse_vet_file(path string, table_ &ast.Table, pref_ &pref.Preferences) (&ast.File, []vet.Error, []vet.Error) { pub fn parse_vet_file(path string, mut table_ ast.Table, pref_ &pref.Preferences) (&ast.File, []vet.Error, []vet.Error) {
$if trace_parse_vet_file ? { $if trace_parse_vet_file ? {
eprintln('> ${@MOD}.${@FN} path: ${path}') eprintln('> ${@MOD}.${@FN} path: ${path}')
} }
@ -367,7 +367,8 @@ pub fn (mut p Parser) parse() &ast.File {
// codegen // codegen
if p.codegen_text.len > 0 && !p.pref.is_fmt { if p.codegen_text.len > 0 && !p.pref.is_fmt {
ptext := 'module ' + p.mod.all_after_last('.') + '\n' + p.codegen_text ptext := 'module ' + p.mod.all_after_last('.') + '\n' + p.codegen_text
codegen_files << parse_text(ptext, p.file_name, p.table, p.comments_mode, p.pref) codegen_files << parse_text(ptext, p.file_name, mut p.table, p.comments_mode,
p.pref)
} }
return &ast.File{ return &ast.File{
@ -427,7 +428,7 @@ fn (mut q Queue) run() {
} }
} }
*/ */
pub fn parse_files(paths []string, table &ast.Table, pref_ &pref.Preferences) []&ast.File { pub fn parse_files(paths []string, mut table ast.Table, pref_ &pref.Preferences) []&ast.File {
mut timers := util.new_timers(should_print: false, label: 'parse_files: ${paths}') mut timers := util.new_timers(should_print: false, label: 'parse_files: ${paths}')
$if time_parsing ? { $if time_parsing ? {
timers.should_print = true timers.should_print = true
@ -459,7 +460,7 @@ pub fn parse_files(paths []string, table &ast.Table, pref_ &pref.Preferences) []
mut files := []&ast.File{cap: paths.len} mut files := []&ast.File{cap: paths.len}
for path in paths { for path in paths {
timers.start('parse_file ${path}') timers.start('parse_file ${path}')
files << parse_file(path, table, .skip_comments, pref_) files << parse_file(path, mut table, .skip_comments, pref_)
timers.show('parse_file ${path}') timers.show('parse_file ${path}')
} }
if codegen_files.len > 0 { if codegen_files.len > 0 {

View file

@ -79,9 +79,9 @@ fn main() {
ff(8+x) ff(8+x)
} }
' '
table := ast.new_table() mut table := ast.new_table()
vpref := &pref.Preferences{} vpref := &pref.Preferences{}
mut prog := parse_text(source_text, '', table, .skip_comments, vpref) mut prog := parse_text(source_text, '', mut table, .skip_comments, vpref)
mut checker_ := checker.new_checker(table, vpref) mut checker_ := checker.new_checker(table, vpref)
checker_.check(mut prog) checker_.check(mut prog)
} }
@ -93,14 +93,14 @@ fn test_one() {
println(@LOCATION) println(@LOCATION)
input := ['a := 10', 'b := -a', 'c := 20'] input := ['a := 10', 'b := -a', 'c := 20']
expected := 'int a = 10;int b = -a;int c = 20;' expected := 'int a = 10;int b = -a;int c = 20;'
table := ast.new_table() mut table := ast.new_table()
vpref := &pref.Preferences{} vpref := &pref.Preferences{}
scope := &ast.Scope{ mut scope := &ast.Scope{
start_pos: 0 start_pos: 0
} }
mut e := []ast.Stmt{} mut e := []ast.Stmt{}
for line in input { for line in input {
e << parse_stmt(line, table, scope) e << parse_stmt(line, mut table, mut scope)
} }
mut program := &ast.File{ mut program := &ast.File{
stmts: e stmts: e
@ -109,7 +109,7 @@ fn test_one() {
} }
mut checker_ := checker.new_checker(table, vpref) mut checker_ := checker.new_checker(table, vpref)
checker_.check(mut program) checker_.check(mut program)
mut res, _, _, _ := c.gen([program], table, vpref) mut res, _, _, _ := c.gen([program], mut table, vpref)
res = res.replace('\n', '').trim_space().after('#endif') res = res.replace('\n', '').trim_space().after('#endif')
println(res) println(res)
ok := expected == res ok := expected == res
@ -136,15 +136,15 @@ fn test_parse_expr() {
'string s = tos3("hi");', 'x = 11;', 'a += 10;', '1.2 + 3.4;', '4 + 4;', '1 + 2 * 5;', 'string s = tos3("hi");', 'x = 11;', 'a += 10;', '1.2 + 3.4;', '4 + 4;', '1 + 2 * 5;',
'-a + 1;', '2 + 2;'] '-a + 1;', '2 + 2;']
mut e := []ast.Stmt{} mut e := []ast.Stmt{}
table := ast.new_table() mut table := ast.new_table()
vpref := &pref.Preferences{} vpref := &pref.Preferences{}
mut chk := checker.new_checker(table, vpref) mut chk := checker.new_checker(table, vpref)
scope := &ast.Scope{ mut scope := &ast.Scope{
start_pos: 0 start_pos: 0
} }
for s in input { for s in input {
println('\n\nst="${s}"') println('\n\nst="${s}"')
e << parse_stmt(s, table, scope) e << parse_stmt(s, mut table, mut scope)
} }
mut program := &ast.File{ mut program := &ast.File{
stmts: e stmts: e
@ -152,7 +152,7 @@ fn test_parse_expr() {
global_scope: scope global_scope: scope
} }
chk.check(mut program) chk.check(mut program)
mut res, _, _, _ := c.gen([program], table, vpref) mut res, _, _, _ := c.gen([program], mut table, vpref)
res = res.after('#endif') res = res.after('#endif')
println('========') println('========')
println(res) println(res)
@ -185,13 +185,13 @@ fn test_num_literals() {
'c := -12.', 'c := -12.',
'd := -a', 'd := -a',
] ]
table := ast.new_table() mut table := ast.new_table()
mut scope := &ast.Scope{ mut scope := &ast.Scope{
start_pos: 0 start_pos: 0
} }
mut rhs_types := []string{} mut rhs_types := []string{}
for input in inputs { for input in inputs {
stmt := parse_stmt(input, table, scope) stmt := parse_stmt(input, mut table, mut scope)
r := (stmt as ast.AssignStmt).right r := (stmt as ast.AssignStmt).right
match r[0] { match r[0] {
ast.IntegerLiteral { rhs_types << 'int literal' } ast.IntegerLiteral { rhs_types << 'int literal' }
@ -297,8 +297,8 @@ fn parse(output_mode pref.OutputMode) ! {
pref_.output_mode = output_mode pref_.output_mode = output_mode
for idx, f in files { for idx, f in files {
// eprintln('> parsing in mode: ${output_mode}, ${idx+1:5}/${files.len} $f ...') // eprintln('> parsing in mode: ${output_mode}, ${idx+1:5}/${files.len} $f ...')
table := ast.new_table() mut table := ast.new_table()
p := parse_file(f, table, .parse_comments, pref_) p := parse_file(f, mut table, .parse_comments, pref_)
assert !isnil(p), 'failed to parse `${f}` in mode: ${output_mode}' assert !isnil(p), 'failed to parse `${f}` in mode: ${output_mode}'
assert p.errors.len == 0, 'file ${f} should have been parsed with 0 errors' assert p.errors.len == 0, 'file ${f} should have been parsed with 0 errors'
} }

View file

@ -13,7 +13,7 @@ fn test_main() {
nr_elems: 11 nr_elems: 11
} }
mut child := Tree{ mut child := Tree{
parent: &parent parent: unsafe { &parent }
} }
child.set_nr_elems('Buzz', 123) child.set_nr_elems('Buzz', 123)
assert child.parent or { return }.nr_elems == 123 assert child.parent or { return }.nr_elems == 123

View file

@ -3,11 +3,13 @@ import v.parser
import v.pref import v.pref
fn test_parser_map_type() { fn test_parser_map_type() {
result := parser.parse_text('a := map[*Node]bool', '', ast.new_table(), .parse_comments, mut table := ast.new_table()
&pref.Preferences{ pref_ := pref.Preferences{
output_mode: .silent output_mode: .silent
is_fmt: true is_fmt: true
}) }
result := parser.parse_text('a := map[*Node]bool', '', mut table, .parse_comments,
pref_)
println(result) println(result)
assert true assert true
} }