diff --git a/vlib/v/ast/comptime_valid_idents.v b/vlib/v/ast/comptime_valid_idents.v index 3d9e00d126..2a242adb53 100644 --- a/vlib/v/ast/comptime_valid_idents.v +++ b/vlib/v/ast/comptime_valid_idents.v @@ -1,5 +1,7 @@ module ast +import v.pref + pub const valid_comptime_if_os = ['windows', 'ios', 'macos', 'mach', 'darwin', 'hpux', 'gnu', 'qnx', 'linux', 'freebsd', 'openbsd', 'netbsd', 'bsd', 'dragonfly', 'android', 'termux', 'solaris', 'haiku', 'serenity', 'vinix', 'plan9', 'wasm32_emscripten'] @@ -22,3 +24,225 @@ fn all_valid_comptime_idents() []string { res << valid_comptime_if_other return res } + +pub fn eval_comptime_not_user_defined_ident(ident string, the_pref &pref.Preferences) !bool { + mut is_true := false + if ident in valid_comptime_if_os { + if ident_enum_val := pref.os_from_string(ident) { + if ident_enum_val == the_pref.os { + is_true = true + } + } + } else if ident in valid_comptime_if_compilers { + is_true = pref.cc_from_string(ident) == the_pref.ccompiler_type + } else if ident in valid_comptime_if_platforms { + match ident { + 'amd64' { + is_true = the_pref.arch == .amd64 + } + 'i386' { + is_true = the_pref.arch == .i386 + } + 'aarch64' { + is_true = the_pref.arch == .arm64 + } + 'arm64' { + is_true = the_pref.arch == .arm64 + } + 'arm32' { + is_true = the_pref.arch == .arm32 + } + 'rv64' { + is_true = the_pref.arch == .rv64 + } + 'rv32' { + is_true = the_pref.arch == .rv32 + } + 's390x' { + is_true = the_pref.arch == .s390x + } + 'ppc64le' { + is_true = the_pref.arch == .ppc64le + } + 'loongarch64' { + is_true = the_pref.arch == .loongarch64 + } + else { + return error('invalid \$if condition: unknown platforms `${ident}`') + } + } + } else if ident in valid_comptime_if_cpu_features { + match ident { + 'x64' { + is_true = the_pref.m64 + } + 'x32' { + is_true = !the_pref.m64 + } + 'little_endian' { + is_true = $if little_endian { true } $else { false } + } + 'big_endian' { + is_true = $if big_endian { true } $else { false } + } + else { + return error('invalid \$if condition: unknown cpu_features `${ident}`') + } + } + } else if ident in valid_comptime_if_other { + match ident { + 'apk' { + is_true = the_pref.is_apk + } + 'js' { + is_true = the_pref.backend.is_js() + } + 'debug' { + is_true = the_pref.is_debug + } + 'prod' { + is_true = the_pref.is_prod + } + 'test' { + is_true = the_pref.is_test + } + 'glibc' { + is_true = the_pref.is_glibc + } + 'prealloc' { + is_true = the_pref.prealloc + } + 'no_bounds_checking' { + is_true = the_pref.no_bounds_checking + } + 'freestanding' { + is_true = the_pref.is_bare && !the_pref.output_cross_c + } + 'threads' { + return error('threads should handle outside of `check_valid_ident()`') + } + 'js_node' { + is_true = the_pref.backend == .js_node + } + 'js_browser' { + is_true = the_pref.backend == .js_browser + } + 'js_freestanding' { + is_true = the_pref.backend == .js_freestanding + } + 'interpreter' { + is_true = the_pref.backend == .interpret + } + 'es5' { + is_true = the_pref.output_es5 + } + 'profile' { + is_true = the_pref.is_prof + } + 'wasm32' { + is_true = the_pref.arch == .wasm32 + } + 'wasm32_wasi' { + is_true = the_pref.os == .wasm32_wasi + } + 'fast_math' { + is_true = the_pref.fast_math + } + 'native' { + is_true = the_pref.backend == .native + } + 'autofree' { + is_true = the_pref.autofree + } + else { + return error('invalid \$if condition: unknown other indent `${ident}`') + } + } + } else if ident in the_pref.compile_defines { + is_true = true + } else { + return error('invalid \$if condition: unknown indent `${ident}`') + } + return is_true +} + +pub const system_ident_map = { + // OS + 'windows': '_WIN32' + 'ios': '__TARGET_IOS__' + 'macos': '__APPLE__' + 'mach': '__MACH__' + 'darwin': '__DARWIN__' + 'hpux': '__HPUX__' + 'gnu': '__GNU__' + 'qnx': '__QNX__' + 'linux': '__linux__' + 'serenity': '__serenity__' + 'plan9': '__plan9__' + 'vinix': '__vinix__' + 'freebsd': '__FreeBSD__' + 'openbsd': '__OpenBSD__' + 'netbsd': '__NetBSD__' + 'bsd': '__BSD__' + 'dragonfly': '__DragonFly__' + 'android': '__ANDROID__' + 'termux': '__TERMUX__' + 'solaris': '__sun' + 'haiku': '__HAIKU__' + // Backend + 'js': '_VJS' + 'wasm32_emscripten': '__EMSCRIPTEN__' + 'native': '_VNATIVE' + // Compiler + 'gcc': '__V_GCC__' + 'tinyc': '__TINYC__' + 'clang': '__clang__' + 'mingw': '__MINGW32__' + 'msvc': '_MSC_VER' + 'cplusplus': '__cplusplus' + // Others + 'threads': '__VTHREADS__' + 'gcboehm': '_VGCBOEHM' + 'debug': '_VDEBUG' + 'prod': '_VPROD' + 'profile': '_VPROFILE' + 'test': '_VTEST' + 'glibc': '__GLIBC__' + 'prealloc': '_VPREALLOC' + 'no_bounds_checking': 'CUSTOM_DEFINE_no_bounds_checking' + 'freestanding': '_VFREESTANDING' + 'autofree': '_VAUTOFREE' + // CPU + 'amd64': '__V_amd64' + 'aarch64': '__V_arm64' + 'arm64': '__V_arm64' // aarch64 alias + 'arm32': '__V_arm32' + 'i386': '__V_x86' + 'rv64': '__V_rv64' + 'riscv64': '__V_rv64' // rv64 alias + 'rv32': '__V_rv32' + 'riscv32': '__V_rv32' // rv32 alias + 's390x': '__V_s390x' + 'ppc64le': '__V_ppc64le' + 'loongarch64': '__V_loongarch64' + 'x64': 'TARGET_IS_64BIT' + 'x32': 'TARGET_IS_32BIT' + 'little_endian': 'TARGET_ORDER_IS_LITTLE' + 'big_endian': 'TARGET_ORDER_IS_BIG' +} + +pub fn comptime_if_to_ifdef(name string, the_pref &pref.Preferences) !string { + if name == 'fast_math' { + return if the_pref.ccompiler_type == .msvc { + // turned on by: `-cflags /fp:fast` + '_M_FP_FAST' + } else { + // turned on by: `-cflags -ffast-math` + '__FAST_MATH__' + } + } + if ifdef := system_ident_map[name] { + return ifdef + } + return error('bad os ifdef name `${name}`') +} diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 1befa70d85..9567c77cf5 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -784,191 +784,6 @@ fn (mut c Checker) evaluate_once_comptime_if_attribute(mut node ast.Attr) bool { return node.ct_skip } -fn (mut c Checker) comptime_if_to_ifdef(name string) !string { - match name { - // platforms/os-es: - 'windows' { - return '_WIN32' - } - 'ios' { - return '__TARGET_IOS__' - } - 'macos' { - return '__APPLE__' - } - 'mach' { - return '__MACH__' - } - 'darwin' { - return '__DARWIN__' - } - 'hpux' { - return '__HPUX__' - } - 'gnu' { - return '__GNU__' - } - 'qnx' { - return '__QNX__' - } - 'linux' { - return '__linux__' - } - 'serenity' { - return '__serenity__' - } - 'plan9' { - return '__plan9__' - } - 'vinix' { - return '__vinix__' - } - 'freebsd' { - return '__FreeBSD__' - } - 'openbsd' { - return '__OpenBSD__' - } - 'netbsd' { - return '__NetBSD__' - } - 'bsd' { - return '__BSD__' - } - 'dragonfly' { - return '__DragonFly__' - } - 'android' { - return '__ANDROID__' - } - 'termux' { - // Note: termux is running on Android natively so __ANDROID__ will also be defined - return '__TERMUX__' - } - 'solaris' { - return '__sun' - } - 'haiku' { - return '__HAIKU__' - } - // - 'js' { - return '_VJS' - } - 'wasm32_emscripten' { - return '__EMSCRIPTEN__' - } - 'native' { - return '_VNATIVE' // when using the native backend, cgen is inactive - } - // compilers: - 'gcc' { - return '__V_GCC__' - } - 'tinyc' { - return '__TINYC__' - } - 'clang' { - return '__clang__' - } - 'mingw' { - return '__MINGW32__' - } - 'msvc' { - return '_MSC_VER' - } - 'cplusplus' { - return '__cplusplus' - } - // other: - 'threads' { - return '__VTHREADS__' - } - 'gcboehm' { - return '_VGCBOEHM' - } - 'debug' { - return '_VDEBUG' - } - 'prod' { - return '_VPROD' - } - 'profile' { - return '_VPROFILE' - } - 'test' { - return '_VTEST' - } - 'glibc' { - return '__GLIBC__' - } - 'prealloc' { - return '_VPREALLOC' - } - 'no_bounds_checking' { - return 'CUSTOM_DEFINE_no_bounds_checking' - } - 'freestanding' { - return '_VFREESTANDING' - } - 'autofree' { - return '_VAUTOFREE' - } - // architectures: - 'amd64' { - return '__V_amd64' - } - 'aarch64', 'arm64' { - return '__V_arm64' - } - 'arm32' { - return '__V_arm32' - } - 'i386' { - return '__V_x86' - } - 'rv64', 'riscv64' { - return '__V_rv64' - } - 'rv32', 'riscv32' { - return '__V_rv32' - } - 's390x' { - return '__V_s390x' - } - 'ppc64le' { - return '__V_ppc64le' - } - 'loongarch64' { - return '__V_loongarch64' - } - // bitness: - 'x64' { - return 'TARGET_IS_64BIT' - } - 'x32' { - return 'TARGET_IS_32BIT' - } - // endianness: - 'little_endian' { - return 'TARGET_ORDER_IS_LITTLE' - } - 'big_endian' { - return 'TARGET_ORDER_IS_BIG' - } - 'fast_math' { - if c.pref.ccompiler_type == .msvc { - // turned on by: `-cflags /fp:fast` - return '_M_FP_FAST' - } - // turned on by: `-cflags -ffast-math` - return '__FAST_MATH__' - } - else {} - } - return error('bad os ifdef name "${name}"') -} - // check if `ident` is a function generic, such as `T` fn (mut c Checker) is_generic_ident(ident string) bool { if !isnil(c.table.cur_fn) && ident in c.table.cur_fn.generic_names @@ -1141,10 +956,6 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) ( should_record_ident = true is_user_ident = true ident_name = cname - // ifdef := c.comptime_if_to_ifdef(cname, true) or { - // c.error(err.msg(), cond.pos) - // return false, false - //} sb.write_string('defined(CUSTOM_DEFINE_${cname})') is_true = cname in c.pref.compile_defines return is_true, false @@ -1483,143 +1294,12 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) ( should_record_ident = true is_user_ident = false ident_name = cname - if cname in ast.valid_comptime_if_os { - if cname_enum_val := pref.os_from_string(cname) { - if cname_enum_val == c.pref.os { - is_true = true - } - } - } else if cname in ast.valid_comptime_if_compilers { - is_true = pref.cc_from_string(cname) == c.pref.ccompiler_type - } else if cname in ast.valid_comptime_if_platforms { - if cname == 'aarch64' { - c.note('use `arm64` instead of `aarch64`', cond.pos) - } - match cname { - 'amd64' { - is_true = c.pref.arch == .amd64 - } - 'i386' { - is_true = c.pref.arch == .i386 - } - 'aarch64' { - is_true = c.pref.arch == .arm64 - } - 'arm64' { - is_true = c.pref.arch == .arm64 - } - 'arm32' { - is_true = c.pref.arch == .arm32 - } - 'rv64' { - is_true = c.pref.arch == .rv64 - } - 'rv32' { - is_true = c.pref.arch == .rv32 - } - 's390x' { - is_true = c.pref.arch == .s390x - } - 'ppc64le' { - is_true = c.pref.arch == .ppc64le - } - 'loongarch64' { - is_true = c.pref.arch == .loongarch64 - } - else { - c.error('invalid \$if condition: unknown platforms `${cname}`', - cond.pos) - return false, false - } - } - } else if cname in ast.valid_comptime_if_cpu_features { - match cname { - 'x64' { - is_true = c.pref.m64 - } - 'x32' { - is_true = !c.pref.m64 - } - 'little_endian' { - is_true = $if little_endian { true } $else { false } - } - 'big_endian' { - is_true = $if big_endian { true } $else { false } - } - else { - c.error('invalid \$if condition: unknown cpu_features `${cname}`', - cond.pos) - return false, false - } - } - } else if cname in ast.valid_comptime_if_other { - match cname { - 'apk' { - is_true = c.pref.is_apk - } - 'js' { - is_true = c.pref.backend.is_js() - } - 'debug' { - is_true = c.pref.is_debug - } - 'prod' { - is_true = c.pref.is_prod - } - 'test' { - is_true = c.pref.is_test - } - 'glibc' { - is_true = c.pref.is_glibc - } - 'prealloc' { - is_true = c.pref.prealloc - } - 'no_bounds_checking' { - is_true = c.pref.no_bounds_checking - } - 'freestanding' { - is_true = c.pref.is_bare && !c.pref.output_cross_c - } - 'threads' { - is_true = c.table.gostmts > 0 - } - 'js_node' { - is_true = c.pref.backend == .js_node - } - 'js_browser' { - is_true = c.pref.backend == .js_browser - } - 'js_freestanding' { - is_true = c.pref.backend == .js_freestanding - } - 'interpreter' { - is_true = c.pref.backend == .interpret - } - 'es5' { - is_true = c.pref.output_es5 - } - 'profile' { - is_true = c.pref.is_prof - } - 'wasm32' { - is_true = c.pref.arch == .wasm32 - } - 'wasm32_wasi' { - is_true = c.pref.os == .wasm32_wasi - } - 'fast_math' { - is_true = c.pref.fast_math - } - 'native' { - is_true = c.pref.backend == .native - } - 'autofree' { - is_true = c.pref.autofree - } - else { - c.error('invalid \$if condition: unknown other indent `${cname}`', - cond.pos) + if cname in ast.valid_comptime_not_user_defined { + if cname == 'threads' { + is_true = c.table.gostmts > 0 + } else { + is_true = ast.eval_comptime_not_user_defined_ident(cname, c.pref) or { + c.error(err.msg(), cond.pos) return false, false } } @@ -1655,7 +1335,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) ( c.error('invalid \$if condition: unknown indent `${cname}`', cond.pos) return false, false } - if ifdef := c.comptime_if_to_ifdef(cname) { + if ifdef := ast.comptime_if_to_ifdef(cname, c.pref) { sb.write_string('defined(${ifdef})') } else { sb.write_string('${is_true}') diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 8ff27d2e17..03b530141e 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -18,39 +18,41 @@ pub struct Fmt { pub: pref &pref.Preferences = unsafe { nil } pub mut: - file ast.File - table &ast.Table = unsafe { nil } - is_debug bool - out strings.Builder - indent int - empty_line bool - line_len int // the current line length, Note: it counts \t as 4 spaces, and starts at 0 after f.writeln - buffering bool // disables line wrapping for exprs that will be analyzed later - par_level int // how many parentheses are put around the current expression - array_init_break []bool // line breaks after elements in hierarchy level of multi dimensional array - array_init_depth int // current level of hierarchy in array init - single_line_if bool - cur_mod string - import_pos int // position of the last import in the resulting string - mod2alias map[string]string // for `import time as t`, will contain: 'time'=>'t' - mod2syms map[string]string // import time { now } 'time.now'=>'now' - implied_import_str string // ​imports that the user's code uses but omitted to import explicitly - processed_imports []string - has_import_stmt bool - use_short_fn_args bool - single_line_fields bool // should struct fields be on a single line - in_lambda_depth int - inside_const bool - inside_unsafe bool - inside_comptime_if bool - is_assign bool - is_index_expr bool - is_mbranch_expr bool // match a { x...y { } } - is_struct_init bool - fn_scope &ast.Scope = unsafe { nil } - wsinfix_depth int - format_state FormatState - source_text string // can be set by `echo "println('hi')" | v fmt`, i.e. when processing source not from a file, but from stdin. In this case, it will contain the entire input text. You can use f.file.path otherwise, and read from that file. + file ast.File + table &ast.Table = unsafe { nil } + is_debug bool + out strings.Builder + indent int + empty_line bool + line_len int // the current line length, Note: it counts \t as 4 spaces, and starts at 0 after f.writeln + buffering bool // disables line wrapping for exprs that will be analyzed later + par_level int // how many parentheses are put around the current expression + array_init_break []bool // line breaks after elements in hierarchy level of multi dimensional array + array_init_depth int // current level of hierarchy in array init + single_line_if bool + cur_mod string + import_pos int // position of the last import in the resulting string + mod2alias map[string]string // for `import time as t`, will contain: 'time'=>'t' + mod2syms map[string]string // import time { now } 'time.now'=>'now' + implied_import_str string // ​imports that the user's code uses but omitted to import explicitly + processed_imports []string + has_import_stmt bool + use_short_fn_args bool + single_line_fields bool // should struct fields be on a single line + in_lambda_depth int + inside_const bool + inside_unsafe bool + inside_comptime_if bool + is_assign bool + is_index_expr bool + is_mbranch_expr bool // match a { x...y { } } + is_struct_init bool + fn_scope &ast.Scope = unsafe { nil } + wsinfix_depth int + format_state FormatState + source_text string // can be set by `echo "println('hi')" | v fmt`, i.e. when processing source not from a file, but from stdin. In this case, it will contain the entire input text. You can use f.file.path otherwise, and read from that file. + global_processed_imports []string + branch_processed_imports []string } @[params] @@ -317,12 +319,17 @@ pub fn (mut f Fmt) import_stmt(imp ast.Import) { return } imp_stmt := f.imp_stmt_str(imp) - if imp_stmt in f.processed_imports { + if imp_stmt in f.global_processed_imports + || (f.inside_comptime_if && imp_stmt in f.branch_processed_imports) { // Skip duplicates. f.import_comments(imp.next_comments) return } - f.processed_imports << imp_stmt + if f.inside_comptime_if { + f.branch_processed_imports << imp_stmt + } else { + f.global_processed_imports << imp_stmt + } if !f.format_state.is_vfmt_on { original_imp_line := f.get_source_lines()#[imp.pos.line_nr..imp.pos.last_line + 1].join('\n') // Same line comments(`imp.comments`) are included in the `original_imp_line`. @@ -1070,7 +1077,7 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { f.writeln('') f.comments(field.next_comments, has_nl: true, level: .indent) } - f.writeln('}\n') + f.writeln('}') } pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { @@ -2354,6 +2361,7 @@ pub fn (mut f Fmt) if_expr(node ast.IfExpr) { start_len := f.line_len for { for i, branch in node.branches { + f.branch_processed_imports.clear() mut sum_len := 0 if i > 0 { // `else`, close previous branch diff --git a/vlib/v/fmt/tests/comptime_if_top_enum_empty_line_keep.vv b/vlib/v/fmt/tests/comptime_if_top_enum_empty_line_keep.vv new file mode 100644 index 0000000000..a3c9dfeab0 --- /dev/null +++ b/vlib/v/fmt/tests/comptime_if_top_enum_empty_line_keep.vv @@ -0,0 +1,16 @@ +$if new_c_1 ? { + pub enum Enum1 { + enum1_a + enum1_b + } +} $else $if new_c_2 ? { + pub enum Enum1 { + enum1_c + enum1_d + } +} $else { + pub enum Enum1 { + enum1_e + enum1_f + } +} diff --git a/vlib/v/fmt/tests/enums_expected.vv b/vlib/v/fmt/tests/enums_expected.vv index 8c30ec2aeb..c71c518f2b 100644 --- a/vlib/v/fmt/tests/enums_expected.vv +++ b/vlib/v/fmt/tests/enums_expected.vv @@ -2,7 +2,6 @@ pub enum PubEnum { foo bar } - enum PrivateEnum { foo bar diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 93010cbfba..228971d8bb 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -331,6 +331,65 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a } } +fn all_global_decl_in_stmts(stmts []ast.Stmt, mut all_fns map[string]ast.FnDecl, mut all_consts map[string]ast.ConstField, mut all_globals map[string]ast.GlobalField, mut all_decltypes map[string]ast.TypeDecl, mut all_structs map[string]ast.StructDecl) { + for node in stmts { + match node { + ast.FnDecl { + fkey := node.fkey() + if fkey !in all_fns || !node.no_body { + all_fns[fkey] = node + } + } + ast.ConstDecl { + for cfield in node.fields { + ckey := cfield.name + all_consts[ckey] = cfield + } + } + ast.GlobalDecl { + for gfield in node.fields { + gkey := gfield.name + all_globals[gkey] = gfield + } + } + ast.StructDecl { + all_structs[node.name] = node + } + ast.TypeDecl { + if node.is_markused { + all_decltypes[node.name] = node + } + } + ast.ExprStmt { + match node.expr { + ast.IfExpr { + if node.expr.is_comptime { + // top level comptime $if + for branch in node.expr.branches { + all_global_decl_in_stmts(branch.stmts, mut all_fns, mut + all_consts, mut all_globals, mut all_decltypes, mut + all_structs) + } + } + } + ast.MatchExpr { + if node.expr.is_comptime { + // top level comptime $match + for branch in node.expr.branches { + all_global_decl_in_stmts(branch.stmts, mut all_fns, mut + all_consts, mut all_globals, mut all_decltypes, mut + all_structs) + } + } + } + else {} + } + } + else {} + } + } +} + fn all_global_decl(ast_files []&ast.File) (map[string]ast.FnDecl, map[string]ast.ConstField, map[string]ast.GlobalField, map[string]ast.TypeDecl, map[string]ast.StructDecl) { util.timing_start(@METHOD) defer { @@ -342,37 +401,8 @@ fn all_global_decl(ast_files []&ast.File) (map[string]ast.FnDecl, map[string]ast mut all_decltypes := map[string]ast.TypeDecl{} mut all_structs := map[string]ast.StructDecl{} for i in 0 .. ast_files.len { - for node in ast_files[i].stmts { - match node { - ast.FnDecl { - fkey := node.fkey() - if fkey !in all_fns || !node.no_body { - all_fns[fkey] = node - } - } - ast.ConstDecl { - for cfield in node.fields { - ckey := cfield.name - all_consts[ckey] = cfield - } - } - ast.GlobalDecl { - for gfield in node.fields { - gkey := gfield.name - all_globals[gkey] = gfield - } - } - ast.StructDecl { - all_structs[node.name] = node - } - ast.TypeDecl { - if node.is_markused { - all_decltypes[node.name] = node - } - } - else {} - } - } + all_global_decl_in_stmts(ast_files[i].stmts, mut all_fns, mut all_consts, mut + all_globals, mut all_decltypes, mut all_structs) } return all_fns, all_consts, all_globals, all_decltypes, all_structs } diff --git a/vlib/v/parser/enum.v b/vlib/v/parser/enum.v index 5ce4120ee1..b3437e44b2 100644 --- a/vlib/v/parser/enum.v +++ b/vlib/v/parser/enum.v @@ -96,7 +96,10 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { // p.warn('enum val $val') if p.tok.kind == .assign { p.next() + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = true expr = p.expr(0) + p.inside_assign_rhs = old_assign_rhs has_expr = true uses_exprs = true } @@ -225,7 +228,8 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { is_pub: is_pub }) - if idx in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] { + if idx in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] + && !p.pref.is_fmt { p.error_with_pos('cannot register enum `${name}`, another type with this name exists', end_pos) } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 46be078400..8dd3ac1594 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -359,6 +359,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { check_name = if language == .js { p.check_js_name() } else { p.check_name() } name = check_name } + if language == .v && !p.pref.translated && !p.is_translated && !p.builtin_mod && util.contains_capital(check_name) { p.error_with_pos('function names cannot contain uppercase letters, use snake_case instead', @@ -655,6 +656,7 @@ run them via `v file.v` instead', }) */ // Body + keep_fn_name := p.cur_fn_name p.cur_fn_name = name mut stmts := []ast.Stmt{} body_start_pos := p.tok.pos() @@ -671,6 +673,7 @@ run them via `v file.v` instead', p.inside_unsafe_fn = false p.inside_fn = false } + p.cur_fn_name = keep_fn_name if !no_body && are_params_type_only { p.error_with_pos('functions with type only params can not have bodies', body_start_pos) return ast.FnDecl{ diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index aeb4375f6b..260be280f4 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -5,6 +5,7 @@ module parser import v.ast import v.token +import v.pkgconfig fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { was_inside_if_expr := p.inside_if_expr @@ -25,6 +26,8 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { mut has_else := false mut comments := []ast.Comment{} mut prev_guard := false + mut comptime_skip_curr_stmts := false + mut comptime_has_true_branch := false for p.tok.kind in [.key_if, .key_else] { p.inside_if = true if is_comptime { @@ -58,12 +61,23 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { is_special: true }) } - branches << ast.IfBranch{ - stmts: p.parse_block_no_scope(false) - pos: start_pos.extend(end_pos) - body_pos: body_pos.extend(p.tok.pos()) - comments: comments - scope: p.scope + if is_comptime && comptime_has_true_branch && !p.pref.is_fmt + && !p.pref.output_cross_c { + p.skip_scope() + branches << ast.IfBranch{ + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.tok.pos()) + comments: comments + scope: p.scope + } + } else { + branches << ast.IfBranch{ + stmts: p.parse_block_no_scope(false) + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.tok.pos()) + comments: comments + scope: p.scope + } } p.close_scope() comments = [] @@ -114,7 +128,10 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { comments << p.eat_comments() p.check(.decl_assign) comments << p.eat_comments() + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = true expr := p.expr(0) + p.inside_assign_rhs = old_assign_rhs if expr !in [ast.CallExpr, ast.IndexExpr, ast.PrefixExpr, ast.SelectorExpr, ast.Ident] { p.error_with_pos('if guard condition expression is illegal, it should return an Option', expr.pos()) @@ -141,6 +158,12 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { p.comptime_if_cond = true p.inside_if_cond = true cond = p.expr(0) + if is_comptime && p.is_in_top_level_comptime(p.inside_assign_rhs) { + comptime_skip_curr_stmts = !p.comptime_if_cond(mut cond) + if !comptime_skip_curr_stmts { + comptime_has_true_branch = true + } + } if mut cond is ast.InfixExpr && !is_comptime { if cond.op in [.key_is, .not_is] { if mut cond.left is ast.Ident { @@ -170,14 +193,25 @@ fn (mut p Parser) if_expr(is_comptime bool, is_expr bool) ast.IfExpr { return ast.IfExpr{} } p.open_scope() - stmts := p.parse_block_no_scope(false) - branches << ast.IfBranch{ - cond: cond - stmts: stmts - pos: start_pos.extend(end_pos) - body_pos: body_pos.extend(p.prev_tok.pos()) - comments: comments - scope: p.scope + if is_comptime && comptime_skip_curr_stmts && !p.pref.is_fmt && !p.pref.output_cross_c { + p.skip_scope() + branches << ast.IfBranch{ + cond: cond + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.prev_tok.pos()) + comments: comments + scope: p.scope + } + } else { + stmts := p.parse_block_no_scope(false) + branches << ast.IfBranch{ + cond: cond + stmts: stmts + pos: start_pos.extend(end_pos) + body_pos: body_pos.extend(p.prev_tok.pos()) + comments: comments + scope: p.scope + } } p.close_scope() if is_guard { @@ -570,3 +604,167 @@ fn (mut p Parser) select_expr() ast.SelectExpr { has_exception: has_else || has_timeout } } + +fn (mut p Parser) comptime_if_cond(mut cond ast.Expr) bool { + mut is_true := false + match mut cond { + ast.BoolLiteral { + return cond.val + } + ast.ParExpr { + return p.comptime_if_cond(mut cond.expr) + } + ast.PrefixExpr { + if cond.op != .not { + p.error('invalid \$if prefix operator, only allow `!`.') + return false + } + return !p.comptime_if_cond(mut cond.right) + } + ast.PostfixExpr { + if cond.op != .question { + p.error('invalid \$if postfix operator, only allow `?`.') + return false + } + if cond.expr !is ast.Ident { + p.error('invalid \$if postfix condition, only allow `Indent`.') + return false + } + cname := (cond.expr as ast.Ident).name + return cname in p.pref.compile_defines + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + l := p.comptime_if_cond(mut cond.left) + r := p.comptime_if_cond(mut cond.right) + // if at least one of the cond has `keep_stmts`, we should keep stmts + return if cond.op == .and { l && r } else { l || r } + } + .eq, .ne, .gt, .lt, .ge, .le { + match mut cond.left { + ast.Ident { + // $if version == 2 + match mut cond.right { + ast.StringLiteral { + match cond.op { + .eq { + is_true = cond.left.str() == cond.right.str() + } + .ne { + is_true = cond.left.str() != cond.right.str() + } + else { + p.error('string type only support `==` and `!=` operator') + return false + } + } + } + ast.IntegerLiteral { + match cond.op { + .eq { + is_true = cond.left.str().i64() == cond.right.val.i64() + } + .ne { + is_true = cond.left.str().i64() != cond.right.val.i64() + } + .gt { + is_true = cond.left.str().i64() > cond.right.val.i64() + } + .lt { + is_true = cond.left.str().i64() < cond.right.val.i64() + } + .ge { + is_true = cond.left.str().i64() >= cond.right.val.i64() + } + .le { + is_true = cond.left.str().i64() <= cond.right.val.i64() + } + else { + p.error('int type only support `==` `!=` `>` `<` `>=` and `<=` operator') + return false + } + } + } + ast.BoolLiteral { + match cond.op { + .eq { + is_true = cond.left.str().bool() == cond.right.val + } + .ne { + is_true = cond.left.str().bool() != cond.right.val + } + else { + p.error('bool type only support `==` and `!=` operator') + return false + } + } + } + else { + p.error('compare only support string int and bool type') + return false + } + } + return is_true + } + else { + p.error('invalid \$if condition') + return false + } + } + p.error('invalid \$if condition') + return false + } + else { + p.error('invalid \$if operator: ${cond.op}') + return false + } + } + } + ast.Ident { + cname := cond.name + if cname in ast.valid_comptime_not_user_defined { + if cname == 'threads' { + is_true = p.table.gostmts > 0 + } else { + is_true = ast.eval_comptime_not_user_defined_ident(cname, p.pref) or { + p.error(err.msg()) + return false + } + } + } else { + p.error('invalid \$if condition: unknown indent `${cname}`') + return false + } + return is_true + } + ast.ComptimeCall { + if cond.kind == .pkgconfig { + if mut m := pkgconfig.main([cond.args_var]) { + if _ := m.run() { + is_true = true + } else { + // pkgconfig not found, do not issue error, just set false + is_true = false + } + } else { + p.error(err.msg()) + is_true = false + } + return is_true + } + if cond.kind == .d { + is_true = cond.compile_value.bool() + return is_true + } + p.error('invalid \$if condition: unknown ComptimeCall') + return false + } + else { + p.error('invalid \$if condition ${cond}') + return false + } + } + + return is_true +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index f22ff8cc37..20abc5cc64 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -111,6 +111,7 @@ mut: generic_type_level int // to avoid infinite recursion segfaults due to compiler bugs in ensure_type_exists main_already_defined bool // TODO move to checker is_vls bool + inside_import_section bool pub mut: scanner &scanner.Scanner = unsafe { nil } table &ast.Table = unsafe { nil } @@ -290,6 +291,7 @@ pub fn (mut p Parser) parse() &ast.File { } else { stmts << module_decl } + p.inside_import_section = true // imports for { if p.tok.kind == .key_import { @@ -451,6 +453,12 @@ fn (mut p Parser) parse_block() []ast.Stmt { return stmts } +fn (mut p Parser) is_in_top_level_comptime(inside_assign_rhs bool) bool { + // TODO: find out a better way detect we are in top level. + return p.cur_fn_name.len == 0 && p.inside_ct_if_expr && !inside_assign_rhs && !p.script_mode + && p.tok.kind != .name +} + fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { p.check(.lcbr) mut stmts := []ast.Stmt{cap: 20} @@ -459,7 +467,12 @@ fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { if p.tok.kind != .rcbr { mut count := 0 for p.tok.kind !in [.eof, .rcbr] { - stmts << p.stmt(is_top_level) + if p.is_in_top_level_comptime(old_assign_rhs) { + // top level `$if cond { println() }` should goto `p.stmt()` + stmts << p.top_stmt() + } else { + stmts << p.stmt(is_top_level) + } count++ if count % 100000 == 0 { if p.is_vls { @@ -617,6 +630,10 @@ fn (p &Parser) trace_parser(label string) { fn (mut p Parser) top_stmt() ast.Stmt { p.trace_parser('top_stmt') for { + if p.tok.kind !in [.key_import, .comment, .dollar] { + // import section should only prepend by `import`, `comment` or `$if`. + p.inside_import_section = false + } match p.tok.kind { .key_pub { match p.peek_tok.kind { @@ -660,8 +677,10 @@ fn (mut p Parser) top_stmt() ast.Stmt { return p.interface_decl() } .key_import { - p.error_with_pos('`import x` can only be declared at the beginning of the file', - p.tok.pos()) + if !p.inside_import_section { + p.error_with_pos('`import x` can only be declared at the beginning of the file', + p.tok.pos()) + } return p.import_stmt() } .key_global { @@ -677,25 +696,52 @@ fn (mut p Parser) top_stmt() ast.Stmt { return p.struct_decl(false) } .dollar { - if p.peek_tok.kind == .eof { - return p.unexpected(got: 'eof') - } - if p.peek_tok.kind == .key_for { - comptime_for_stmt := p.comptime_for() - return p.other_stmts(comptime_for_stmt) - } else if p.peek_tok.kind == .key_if { - if_expr := p.if_expr(true, false) - cur_stmt := ast.ExprStmt{ - expr: if_expr - pos: if_expr.pos + match p.peek_tok.kind { + .eof { + return p.unexpected(got: 'eof') } - if p.pref.is_fmt || comptime_if_expr_contains_top_stmt(if_expr) { - return cur_stmt - } else { - return p.other_stmts(cur_stmt) + .key_for { + comptime_for_stmt := p.comptime_for() + return p.other_stmts(comptime_for_stmt) + } + .key_if { + if_expr := p.if_expr(true, false) + cur_stmt := ast.ExprStmt{ + expr: if_expr + pos: if_expr.pos + } + if p.pref.is_fmt || comptime_if_expr_contains_top_stmt(if_expr) { + return cur_stmt + } else { + return p.other_stmts(cur_stmt) + } + } + .key_match { + mut pos := p.tok.pos() + expr := p.match_expr(true) + pos.update_last_line(p.prev_tok.line_nr) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + .name { + // handles $dbg directly without registering token + if p.peek_tok.lit == 'dbg' { + return p.dbg_stmt() + } else { + mut pos := p.tok.pos() + expr := p.expr(0) + pos.update_last_line(p.prev_tok.line_nr) + return ast.ExprStmt{ + expr: expr + pos: pos + } + } + } + else { + return p.unexpected() } - } else { - return p.unexpected() } } .hash { @@ -2470,7 +2516,10 @@ fn (mut p Parser) const_decl() ast.ConstDecl { } mut expr := ast.Expr{} if !is_virtual_c_const { + old_inside_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = true expr = p.expr(0) + p.inside_assign_rhs = old_inside_assign_rhs } if is_block { end_comments << p.eat_comments(same_line: true) @@ -2596,7 +2645,10 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { mut typ_pos := token.Pos{} if has_expr { p.next() // = + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = true expr = p.expr(0) + p.inside_assign_rhs = old_assign_rhs match mut expr { ast.CastExpr, ast.StructInit, ast.ArrayInit, ast.ChanInit { typ = expr.typ @@ -2759,7 +2811,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl { } is_pub: is_pub }) - if typ in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] { + if typ in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] + && !p.pref.is_fmt { p.error_with_pos('cannot register sum type `${name}`, another type with this name exists', name_pos) return ast.SumTypeDecl{} @@ -2806,7 +2859,8 @@ fn (mut p Parser) type_decl() ast.TypeDecl { is_pub: is_pub }) type_end_pos := p.prev_tok.pos() - if idx in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] { + if idx in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx] + && !p.pref.is_fmt { p.error_with_pos('cannot register alias `${name}`, another type with this name exists', name_pos) return ast.AliasTypeDecl{} @@ -3017,3 +3071,23 @@ fn (mut p Parser) add_defer_var(ident ast.Ident) { } } } + +// skip `{...}` +fn (mut p Parser) skip_scope() { + mut br_cnt := 0 + for { + match p.tok.kind { + .lcbr { br_cnt++ } + .rcbr { br_cnt-- } + .eof { break } + else {} + } + if br_cnt == 0 { + break + } + p.next() + } + if p.tok.kind == .rcbr { + p.next() + } +} diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index df1cee88ba..87a8b560d0 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -338,7 +338,10 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { if p.tok.kind == .assign { // Default value p.next() + old_assign_rhs := p.inside_assign_rhs + p.inside_assign_rhs = true default_expr = p.expr(0) + p.inside_assign_rhs = old_assign_rhs match mut default_expr { ast.EnumVal { default_expr.typ = typ } // TODO: implement all types?? @@ -457,7 +460,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { } } // allow duplicate c struct declarations - if ret == -1 && language != .c { + if ret == -1 && language != .c && !p.pref.is_fmt { p.error_with_pos('cannot register struct `${name}`, another type with this name exists', name_pos) return ast.StructDecl{} @@ -671,7 +674,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { } language: language ) - if reg_idx == -1 { + if reg_idx == -1 && !p.pref.is_fmt { p.error_with_pos('cannot register interface `${interface_name}`, another type with this name exists', name_pos) return ast.InterfaceDecl{} diff --git a/vlib/v/tests/comptime/comptime_if_top_1_test.v b/vlib/v/tests/comptime/comptime_if_top_1_test.v new file mode 100644 index 0000000000..282d05f012 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_if_top_1_test.v @@ -0,0 +1,94 @@ +// vtest vflags: -d new_1 -d new_a_1 -d new_b_1 -d new_c_1 -d new_d_1 -d new_e_1 +module main + +// this is comment, should skip + +$if new_1 ? { + // this is comment, should skip + import os + // this is comment, should skip +} $else $if new_2 ? { + // this is comment, should skip + import math + // this is comment, should skip +} $else { + // this is comment, should skip + import time + // this is comment, should skip +} +// this is comment, should skip + +const t = $if amd64 { 1 } $else { 2 } + +$if new_a_1 ? { + pub type Digits = u64 +} $else $if new_a_2 ? { + pub type Digits = u32 +} $else { + pub type Digits = u8 +} + +$if new_b_1 ? { + pub const const1 = '123' +} $else $if new_b_2 ? { + pub const const1 = 123 +} $else { + pub const const1 = 1.1 +} + +$if new_c_1 ? { + pub enum Enum1 { + enum1_a + enum1_b + } +} $else $if new_c_2 ? { + pub enum Enum1 { + enum1_c + enum1_d + } +} $else { + pub enum Enum1 { + enum1_e + enum1_f + } +} + +$if new_d_1 ? { + pub struct Struct1 { + a int + } +} $else $if new_d_2 ? { + pub struct Struct1 { + b int + } +} $else { + pub struct Struct1 { + c int + } +} + +$if new_e_1 ? { + pub fn ret() string { + return 'new_e_1' + } +} $else $if new_e_2 ? { + pub fn ret() string { + return 'new_e_2' + } +} $else { + pub fn ret() string { + return 'new_e_3' + } +} + +fn test_main() { + assert os.user_os().len > 0 + assert t in [1, 2] + assert sizeof(Digits) == 8 // Digits == u64 + assert const1 == '123' + _ := Enum1.enum1_a // should compile + _ := Struct1{ + a: 123 + } // should compile + assert ret() == 'new_e_1' +} diff --git a/vlib/v/tests/comptime/comptime_if_top_2_test.v b/vlib/v/tests/comptime/comptime_if_top_2_test.v new file mode 100644 index 0000000000..39ff6d949f --- /dev/null +++ b/vlib/v/tests/comptime/comptime_if_top_2_test.v @@ -0,0 +1,94 @@ +// vtest vflags: -d new_2 -d new_a_2 -d new_b_2 -d new_c_2 -d new_d_2 -d new_e_2 +module main + +// this is comment, should skip + +$if new_1 ? { + // this is comment, should skip + import os + // this is comment, should skip +} $else $if new_2 ? { + // this is comment, should skip + import math + // this is comment, should skip +} $else { + // this is comment, should skip + import time + // this is comment, should skip +} +// this is comment, should skip + +const t = $if amd64 { 1 } $else { 2 } + +$if new_a_1 ? { + pub type Digits = u64 +} $else $if new_a_2 ? { + pub type Digits = u32 +} $else { + pub type Digits = u8 +} + +$if new_b_1 ? { + pub const const1 = '123' +} $else $if new_b_2 ? { + pub const const1 = 123 +} $else { + pub const const1 = 1.1 +} + +$if new_c_1 ? { + pub enum Enum1 { + enum1_a + enum1_b + } +} $else $if new_c_2 ? { + pub enum Enum1 { + enum1_c + enum1_d + } +} $else { + pub enum Enum1 { + enum1_e + enum1_f + } +} + +$if new_d_1 ? { + pub struct Struct1 { + a int + } +} $else $if new_d_2 ? { + pub struct Struct1 { + b int + } +} $else { + pub struct Struct1 { + c int + } +} + +$if new_e_1 ? { + pub fn ret() string { + return 'new_e_1' + } +} $else $if new_e_2 ? { + pub fn ret() string { + return 'new_e_2' + } +} $else { + pub fn ret() string { + return 'new_e_3' + } +} + +fn test_main() { + assert math.max(1, 2) == 2 + assert t in [1, 2] + assert sizeof(Digits) == 4 // Digits == u32 + assert const1 == 123 + _ := Enum1.enum1_d // should compile + _ := Struct1{ + b: 123 + } // should compile + assert ret() == 'new_e_2' +} diff --git a/vlib/v/tests/comptime/comptime_if_top_3_test.v b/vlib/v/tests/comptime/comptime_if_top_3_test.v new file mode 100644 index 0000000000..0d0178eb45 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_if_top_3_test.v @@ -0,0 +1,94 @@ +// vtest vflags: -d new_3 -d new_a_3 -d new_b_3 -d new_c_3 -d new_d_3 -d new_e_3 +module main + +// this is comment, should skip + +$if new_1 ? { + // this is comment, should skip + import os + // this is comment, should skip +} $else $if new_2 ? { + // this is comment, should skip + import math + // this is comment, should skip +} $else { + // this is comment, should skip + import time + // this is comment, should skip +} +// this is comment, should skip + +const t = $if amd64 { 1 } $else { 2 } + +$if new_a_1 ? { + pub type Digits = u64 +} $else $if new_a_2 ? { + pub type Digits = u32 +} $else { + pub type Digits = u8 +} + +$if new_b_1 ? { + pub const const1 = '123' +} $else $if new_b_2 ? { + pub const const1 = 123 +} $else { + pub const const1 = 1.1 +} + +$if new_c_1 ? { + pub enum Enum1 { + enum1_a + enum1_b + } +} $else $if new_c_2 ? { + pub enum Enum1 { + enum1_c + enum1_d + } +} $else { + pub enum Enum1 { + enum1_e + enum1_f + } +} + +$if new_d_1 ? { + pub struct Struct1 { + a int + } +} $else $if new_d_2 ? { + pub struct Struct1 { + b int + } +} $else { + pub struct Struct1 { + c int + } +} + +$if new_e_1 ? { + pub fn ret() string { + return 'new_e_1' + } +} $else $if new_e_2 ? { + pub fn ret() string { + return 'new_e_2' + } +} $else { + pub fn ret() string { + return 'new_e_3' + } +} + +fn test_main() { + assert time.days_in_year == 365 + assert t in [1, 2] + assert sizeof(Digits) == 1 // Digits == u8 + assert const1 == 1.1 + _ := Enum1.enum1_e // should compile + _ := Struct1{ + c: 123 + } // should compile + assert ret() == 'new_e_3' +}