diff --git a/cmd/v/help/build.txt b/cmd/v/help/build.txt index 31a2bb86cb..7f884a5fea 100644 --- a/cmd/v/help/build.txt +++ b/cmd/v/help/build.txt @@ -126,8 +126,15 @@ The build flags are shared by the build and run commands: Treat all warnings as errors, even in development builds. -Wfatal-errors - Unconditionally exit with exit(1) after the first error. Useful for scripts/tooling that calls V. + Unconditionally exit with exit(1) after the first error. + Useful for scripts/tooling that calls V. + -Wimpure-v + Warn about using C. or JS. symbols in plain .v files. + These should be moved in .c.v and .js.v . + NB: in the future, this will be turned on by default, + and will become an error, after vlib is cleaned up. + For C-specific build flags, use `v help build-c`. See also: diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index d7514759d6..1d77a66147 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -20,6 +20,9 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp } else { p.check_name() } + if language != .v { + p.check_for_impure_v(language, first_pos) + } mut or_kind := ast.OrKind.absent if fn_name == 'json.decode' { p.expecting_type = true // Makes name_expr() parse the type `User` in `json.decode(User, txt)` @@ -155,6 +158,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { if language != .v { p.next() p.check(.dot) + p.check_for_impure_v(language, p.tok.position()) } // Receiver? mut rec_name := '' diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 4e5227a777..b0c12eb6ff 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -15,11 +15,12 @@ import runtime import time pub struct Parser { + pref &pref.Preferences +mut: file_base string // "hello.v" file_name string // "/home/user/hello.v" file_name_dir string // "/home/user" - pref &pref.Preferences -mut: + file_backend_mode table.Language // .c for .c.v|.c.vv|.c.vsh files; .js for .js.v files, .v otherwise. scanner &scanner.Scanner comments_mode scanner.CommentsMode = .skip_comments // see comment in parse_file @@ -101,9 +102,6 @@ pub fn parse_text(text string, path string, table &table.Table, comments_mode sc mut p := Parser{ scanner: s comments_mode: comments_mode - file_name: path - file_base: os.base(path) - file_name_dir: os.dir(path) table: table pref: pref scope: &ast.Scope{ @@ -114,9 +112,23 @@ pub fn parse_text(text string, path string, table &table.Table, comments_mode sc warnings: []errors.Warning{} global_scope: global_scope } + p.set_path(path) return p.parse() } +pub fn (mut p Parser) set_path(path string) { + p.file_name = path + p.file_base = os.base(path) + p.file_name_dir = os.dir(path) + p.file_backend_mode = .v + if path.ends_with('.c.v') || path.ends_with('.c.vv') || path.ends_with('.c.vsh') { + p.file_backend_mode = .c + } + if path.ends_with('.js.v') || path.ends_with('.js.vv') || path.ends_with('.js.vsh') { + p.file_backend_mode = .js + } +} + pub fn parse_file(path string, table &table.Table, comments_mode scanner.CommentsMode, pref &pref.Preferences, global_scope &ast.Scope) ast.File { // NB: when comments_mode == .toplevel_comments, // the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip @@ -130,9 +142,6 @@ pub fn parse_file(path string, table &table.Table, comments_mode scanner.Comment scanner: scanner.new_scanner_file(path, comments_mode, pref) comments_mode: comments_mode table: table - file_name: path - file_base: os.base(path) - file_name_dir: os.dir(path) pref: pref scope: &ast.Scope{ start_pos: 0 @@ -142,6 +151,7 @@ pub fn parse_file(path string, table &table.Table, comments_mode scanner.Comment warnings: []errors.Warning{} global_scope: global_scope } + p.set_path(path) return p.parse() } @@ -153,9 +163,6 @@ pub fn parse_vet_file(path string, table_ &table.Table, pref &pref.Preferences) scanner: scanner.new_vet_scanner_file(path, .parse_comments, pref) comments_mode: .parse_comments table: table_ - file_name: path - file_base: os.base(path) - file_name_dir: os.dir(path) pref: pref scope: &ast.Scope{ start_pos: 0 @@ -165,6 +172,7 @@ pub fn parse_vet_file(path string, table_ &table.Table, pref &pref.Preferences) warnings: []errors.Warning{} global_scope: global_scope } + p.set_path(path) if p.scanner.text.contains('\n ') { source_lines := os.read_lines(path) or { []string{} } for lnumber, line in source_lines { @@ -845,6 +853,29 @@ fn (mut p Parser) parse_attr() table.Attr { } } +pub fn (mut p Parser) check_for_impure_v(language table.Language, pos token.Position) { + if language == .v { + // pure V code is always allowed everywhere + return + } + if !p.pref.warn_impure_v { + // the stricter mode is not ON yet => allow everything for now + return + } + if p.file_backend_mode != language { + upcase_language := language.str().to_upper() + if p.file_backend_mode == .v { + p.warn_with_pos('$upcase_language code will not be allowed in pure .v files, please move it to a .${language}.v file instead', + pos) + return + } else { + p.warn_with_pos('$upcase_language code is not allowed in .${p.file_backend_mode}.v files, please move it to a .${language}.v file', + pos) + return + } + } +} + pub fn (mut p Parser) error(s string) { p.error_with_pos(s, p.tok.position()) } @@ -1016,12 +1047,13 @@ pub fn (mut p Parser) name_expr() ast.Expr { pos: type_pos } } - language := if p.tok.lit == 'C' { - table.Language.c + mut language := table.Language.v + if p.tok.lit == 'C' { + language = table.Language.c + p.check_for_impure_v(language, p.tok.position()) } else if p.tok.lit == 'JS' { - table.Language.js - } else { - table.Language.v + language = table.Language.js + p.check_for_impure_v(language, p.tok.position()) } mut mod := '' // p.warn('resetting') @@ -2030,12 +2062,13 @@ fn (mut p Parser) type_decl() ast.TypeDecl { parent_type := first_type parent_name := p.table.get_type_symbol(parent_type).name pid := parent_type.idx() - language := if parent_name.len > 2 && parent_name.starts_with('C.') { - table.Language.c + mut language := table.Language.v + if parent_name.len > 2 && parent_name.starts_with('C.') { + language = table.Language.c + p.check_for_impure_v(language, decl_pos) } else if parent_name.len > 2 && parent_name.starts_with('JS.') { - table.Language.js - } else { - table.Language.v + language = table.Language.js + p.check_for_impure_v(language, decl_pos) } prepend_mod_name := p.prepend_mod(name) p.table.register_type_symbol(table.TypeSymbol{ diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index ee9c2a94db..db48f376eb 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -36,6 +36,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { p.next() // . } name_pos := p.tok.position() + p.check_for_impure_v(language, name_pos) mut name := p.check_name() // defer { // if name.contains('App') { diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index d30c8a9f31..cd162f9432 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -121,7 +121,8 @@ pub mut: printfn_list []string // a list of generated function names, whose source should be shown, for debugging print_v_files bool // when true, just print the list of all parsed .v files then stop. skip_running bool // when true, do no try to run the produced file (set by b.cc(), when -o x.c or -o x.js) - skip_warnings bool // like C's "-w" + skip_warnings bool // like C's "-w", forces warnings to be ignored. + warn_impure_v bool // -Wimpure-v, force a warning for JS.fn()/C.fn(), outside of .js.v/.c.v files. TODO: turn to an error by default warns_are_errors bool // -W, like C's "-Werror", treat *every* warning is an error fatal_errors bool // unconditionally exit after the first error with exit(1) reuse_tmpc bool // do not use random names for .tmp.c and .tmp.c.rsp files, and do not remove them @@ -173,6 +174,9 @@ pub fn parse_args(args []string) (&Preferences, string) { '-progress' { // processed by testing tools in cmd/tools/modules/testing/common.v } + '-Wimpure-v' { + res.warn_impure_v = true + } '-Wfatal-errors' { res.fatal_errors = true }