diff --git a/CHANGELOG.md b/CHANGELOG.md index d139c2701a..553359bf26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ ## V 0.3.1 *Not released yet* +- Anonymous structs. - V can now find code in the `src/` directory. This allows making V repos much cleaner. - `os.mkdir()` now has an optional `mode` paramter. - Full termux support via `$if termux {`. - Go backend fixes. +- More type checks. +- New keyword/type: `nil`. Only to be used inside `unsafe`. Replaces `voidptr(0)`. +- DOOM is now translated/compiled and launched on CI servers. A screenshot of the running game + is made via `vgret` and is compared to the expected result. +- VLS performance improvements, especially on Windows. ## V 0.3 *30 Jun 2022* diff --git a/ROADMAP.md b/ROADMAP.md index 530f95be7d..485edfc919 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,8 +5,12 @@ - [ ] Parallel checker - [ ] Parallel C compilation - [ ] `recover()` from panics -- [ ] vfmt: fix common errors automatically (make vars mutable and vice versa, add missing imports) +- [ ] vfmt: add missing imports (like goimports) - [ ] merge v.c and v_win.c - [ ] Recursive structs via optionals: `struct Node { next ?Node }` - [ ] Handle function pointers safely, remove `if function == 0 {` - [ ] Bundle OpenSSL like GC +- [ ] Anonymous structs +- [ ] -usecache on by default +- [ ] -skip-unused on by default + diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 380a411426..83fa231661 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -1020,6 +1020,7 @@ pub mut: is_union bool is_heap bool is_minify bool + is_anon bool is_generic bool generic_types []Type concrete_types []Type diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index ea9622fcd2..260b1fe733 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -5,12 +5,12 @@ module checker import v.ast pub fn (mut c Checker) struct_decl(mut node ast.StructDecl) { - if node.language == .v && !c.is_builtin_mod { - c.check_valid_pascal_case(node.name, 'struct name', node.pos) - } mut struct_sym, struct_typ_idx := c.table.find_sym_and_type_idx(node.name) mut has_generic_types := false if mut struct_sym.info is ast.Struct { + if node.language == .v && !c.is_builtin_mod && !struct_sym.info.is_anon { + c.check_valid_pascal_case(node.name, 'struct name', node.pos) + } for embed in node.embeds { if embed.typ.has_flag(.generic) { has_generic_types = true diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index b448f8698c..2f895b40d5 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4927,96 +4927,7 @@ fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { mut name := sym.cname match sym.info { ast.Struct { - if sym.info.is_generic { - continue - } - if name.contains('_T_') { - g.typedefs.writeln('typedef struct $name $name;') - } - // TODO avoid buffer manip - start_pos := g.type_definitions.len - - mut pre_pragma := '' - mut post_pragma := '' - - for attr in sym.info.attrs { - match attr.name { - '_pack' { - pre_pragma += '#pragma pack(push, $attr.arg)\n' - post_pragma += '#pragma pack(pop)' - } - else {} - } - } - - is_minify := sym.info.is_minify - g.type_definitions.writeln(pre_pragma) - - if sym.info.is_union { - g.type_definitions.writeln('union $name {') - } else { - g.type_definitions.writeln('struct $name {') - } - if sym.info.fields.len > 0 || sym.info.embeds.len > 0 { - for field in sym.info.fields { - // Some of these structs may want to contain - // optionals that may not be defined at this point - // if this is the case then we are going to - // buffer manip out in front of the struct - // write the optional in and then continue - // FIXME: for parallel cgen (two different files using the same optional in struct fields) - if field.typ.has_flag(.optional) { - // Dont use g.typ() here becuase it will register - // optional and we dont want that - styp, base := g.optional_type_name(field.typ) - lock g.done_optionals { - if base !in g.done_optionals { - g.done_optionals << base - last_text := g.type_definitions.after(start_pos).clone() - g.type_definitions.go_back_to(start_pos) - g.typedefs.writeln('typedef struct $styp $styp;') - g.type_definitions.writeln('${g.optional_type_text(styp, - base)};') - g.type_definitions.write_string(last_text) - } - } - } - type_name := g.typ(field.typ) - field_name := c_name(field.name) - volatile_prefix := if field.is_volatile { 'volatile ' } else { '' } - mut size_suffix := '' - if is_minify && !g.is_cc_msvc { - if field.typ == ast.bool_type_idx { - size_suffix = ' : 1' - } else { - field_sym := g.table.sym(field.typ) - if field_sym.info is ast.Enum { - if !field_sym.info.is_flag && !field_sym.info.uses_exprs { - mut bits_needed := 0 - mut l := field_sym.info.vals.len - for l > 0 { - bits_needed++ - l >>= 1 - } - size_suffix = ' : $bits_needed' - } - } - } - } - g.type_definitions.writeln('\t$volatile_prefix$type_name $field_name$size_suffix;') - } - } else { - g.type_definitions.writeln('\tEMPTY_STRUCT_DECLARATION;') - } - // g.type_definitions.writeln('} $name;\n') - // - ti_attrs := if sym.info.attrs.contains('packed') { - '__attribute__((__packed__))' - } else { - '' - } - g.type_definitions.writeln('}$ti_attrs;\n') - g.type_definitions.writeln(post_pragma) + g.struct_decl(sym.info, name, false) } ast.Alias { // ast.Alias { TODO diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index f1f78eb4df..8e4a916deb 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -311,3 +311,114 @@ fn (mut g Gen) is_empty_struct(t Type) bool { } } } + +fn (mut g Gen) struct_decl(s ast.Struct, name string, is_anon bool) { + if s.is_generic { + return + } + if name.contains('_T_') { + g.typedefs.writeln('typedef struct $name $name;') + } + // TODO avoid buffer manip + start_pos := g.type_definitions.len + + mut pre_pragma := '' + mut post_pragma := '' + + for attr in s.attrs { + match attr.name { + '_pack' { + pre_pragma += '#pragma pack(push, $attr.arg)\n' + post_pragma += '#pragma pack(pop)' + } + else {} + } + } + + is_minify := s.is_minify + g.type_definitions.writeln(pre_pragma) + + if is_anon { + g.type_definitions.writeln('struct {') + } else if s.is_union { + g.type_definitions.writeln('union $name {') + } else { + g.type_definitions.writeln('struct $name {') + } + if s.fields.len > 0 || s.embeds.len > 0 { + for field in s.fields { + // Some of these structs may want to contain + // optionals that may not be defined at this point + // if this is the case then we are going to + // buffer manip out in front of the struct + // write the optional in and then continue + // FIXME: for parallel cgen (two different files using the same optional in struct fields) + if field.typ.has_flag(.optional) { + // Dont use g.typ() here becuase it will register + // optional and we dont want that + styp, base := g.optional_type_name(field.typ) + lock g.done_optionals { + if base !in g.done_optionals { + g.done_optionals << base + last_text := g.type_definitions.after(start_pos).clone() + g.type_definitions.go_back_to(start_pos) + g.typedefs.writeln('typedef struct $styp $styp;') + g.type_definitions.writeln('${g.optional_type_text(styp, base)};') + g.type_definitions.write_string(last_text) + } + } + } + type_name := g.typ(field.typ) + field_name := c_name(field.name) + volatile_prefix := if field.is_volatile { 'volatile ' } else { '' } + mut size_suffix := '' + if is_minify && !g.is_cc_msvc { + if field.typ == ast.bool_type_idx { + size_suffix = ' : 1' + } else { + field_sym := g.table.sym(field.typ) + if field_sym.info is ast.Enum { + if !field_sym.info.is_flag && !field_sym.info.uses_exprs { + mut bits_needed := 0 + mut l := field_sym.info.vals.len + for l > 0 { + bits_needed++ + l >>= 1 + } + size_suffix = ' : $bits_needed' + } + } + } + } + field_sym := g.table.sym(field.typ) + mut field_is_anon := false + if field_sym.info is ast.Struct { + if field_sym.info.is_anon { + field_is_anon = true + // Recursively generate code for this anon struct (this is the field's type) + g.struct_decl(field_sym.info, field_sym.cname, true) + // Now the field's name + g.type_definitions.writeln(' $field_name$size_suffix;') + } + } + if !field_is_anon { + g.type_definitions.writeln('\t$volatile_prefix$type_name $field_name$size_suffix;') + } + } + } else { + g.type_definitions.writeln('\tEMPTY_STRUCT_DECLARATION;') + } + // g.type_definitions.writeln('} $name;\n') + // + ti_attrs := if s.attrs.contains('packed') { + '__attribute__((__packed__))' + } else { + '' + } + g.type_definitions.write_string('}$ti_attrs') + if !is_anon { + g.type_definitions.write_string(';') + } + g.type_definitions.writeln('\n') + g.type_definitions.writeln(post_pragma) +} diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 273871a729..5b6ee8970b 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -425,9 +425,9 @@ pub fn (mut p Parser) parse_type() ast.Type { } // Anon structs if p.tok.kind == .key_struct { - p.struct_decl(true) - p.next() - return p.table.find_type_idx('anon_struct') // TODO + struct_decl := p.struct_decl(true) + // Find the registered anon struct type, it was registered above in `p.struct_decl()` + return p.table.find_type_idx(struct_decl.name) } language := p.parse_language() diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index e0b4e6da1b..1f321e3012 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -91,6 +91,7 @@ mut: if_cond_comments []ast.Comment script_mode bool script_mode_start_token token.Token + anon_struct_counter int pub mut: scanner &scanner.Scanner errors []errors.Error diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 36c5fa9e96..2d5e315b5e 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -39,7 +39,12 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { if p.disallow_declarations_in_script_mode() { return ast.StructDecl{} } - mut name := if is_anon { '' } else { p.check_name() } + mut name := if is_anon { + p.anon_struct_counter++ + '_VAnonStruct$p.anon_struct_counter' + } else { + p.check_name() + } if name.len == 1 && name[0].is_capital() { p.error_with_pos('single letter capital names are reserved for generic template types.', name_pos) @@ -61,7 +66,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { return ast.StructDecl{} } if language == .v && !p.builtin_mod && !p.is_translated && name.len > 0 && !name[0].is_capital() - && !p.pref.translated && !p.is_translated { + && !p.pref.translated && !p.is_translated && !is_anon { p.error_with_pos('struct name `$name` must begin with capital letter', name_pos) return ast.StructDecl{} } @@ -338,6 +343,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { is_generic: generic_types.len > 0 generic_types: generic_types attrs: attrs + is_anon: is_anon } is_pub: is_pub } diff --git a/vlib/v/tests/struct_test.v b/vlib/v/tests/struct_test.v index 56f61ba06f..1bbe50eed0 100644 --- a/vlib/v/tests/struct_test.v +++ b/vlib/v/tests/struct_test.v @@ -412,3 +412,15 @@ fn test_struct_update() { assert c2.capital.name == 'city' assert c2.name == 'test' } + +// Test anon structs +struct Book { + x Foo + title string + author struct { + name string + age int + } +} + +fn test_anon() {}