From db2f88cff9129f454afc8161844f6261888cd2ef Mon Sep 17 00:00:00 2001 From: thetarnav Date: Sat, 29 Aug 2026 19:22:18 +0200 Subject: [PATCH] Improve parsing field docs and comments (fixes #5353) --- core/odin/parser/parser.odin | 21 +++----- tests/core/odin/test_parser.odin | 89 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 6929d59ac..73f17c376 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -318,9 +318,6 @@ consume_comment :: proc(p: ^Parser) -> (tok: tokenizer.Token, end_line: int) { } _ = next_token0(p) - if p.curr_tok.pos.line > tok.pos.line { - end_line += 1 - } return } @@ -348,19 +345,13 @@ consume_comment_groups :: proc(p: ^Parser, prev: tokenizer.Token) { if p.curr_tok.kind != .Comment { return } - comment: ^ast.Comment_Group - end_line := 0 if p.curr_tok.pos.line == prev.pos.line { - comment, end_line = consume_comment_group(p, 0) - if p.curr_tok.pos.line != end_line || - p.curr_tok.pos.line == prev.pos.line+1 || - p.curr_tok.kind == .EOF { - p.line_comment = comment - } + p.line_comment, _ = consume_comment_group(p, 0) } - end_line = -1 + comment: ^ast.Comment_Group + end_line: int for p.curr_tok.kind == .Comment { comment, end_line = consume_comment_group(p, 1) } @@ -2048,13 +2039,14 @@ parse_field_list :: proc(p: ^Parser, follow: tokenizer.Token_Kind, allowed_flags } } + line_comment := p.line_comment ok := expect_field_separator(p, type) field := new_ast_field(names, type, default_value) field.tag = tag field.docs = docs field.flags = flags - field.comment = p.line_comment + field.comment = p.line_comment if p.line_comment != nil else line_comment append(fields, field) return ok @@ -3862,6 +3854,8 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou end := p.prev_tok + end_comment := p.line_comment + if p.expr_level >= 0 { end: ^ast.Expr if !is_mutable && len(values) > 0 { @@ -3883,6 +3877,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end)) decl.docs = docs + decl.comment = end_comment decl.names = names decl.type = type decl.values = values diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index e60a9f8f0..05957ecf9 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -65,6 +65,95 @@ Foo :: bit_field uint { ok := parser.parse_file(&p, &file) testing.expect(t, ok, "bad parse") + testing.expect(t, file.syntax_error_count == 0, "should contain zero errors") +} + +@test +test_parse_struct_field_comments :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + expect_comments :: proc (t: ^testing.T, docs: ^ast.Comment_Group, expected: []string, loc := #caller_location) { + if expected == nil { + testing.expect_value(t, docs, nil, loc) + } else { + testing.expect(t, docs != nil, "comment should not be nil", loc=loc) + testing.expect_value(t, len(docs.list), len(expected), loc) + for tok, i in docs.list { + testing.expect_value(t, tok.text, expected[i], loc) + } + } + } + + file := ast.File{ + fullpath = "test.odin", + src = ` +package main + +// foo doc +Foo :: struct { + // doc1 + a: int, // c1 + b: int, // c2 + // not included + + // doc2 + // doc3 + c, d: int, /* c3 +c4 */ + + e: struct { + } // c5 + // not included +} + +// not included + +Bar :: struct {x, y: int /* c4 */} // after`, + } + + p := parser.default_parser() + + p.err = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.errorf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + + p.warn = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.warnf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + + ok := parser.parse_file(&p, &file) + + testing.expect(t, ok, "bad parse") + testing.expect(t, file.syntax_error_count == 0, "should contain zero errors") + + testing.expect_value(t, len(file.decls), 2) + + foo_decl := file.decls[0].derived.(^ast.Value_Decl) + expect_comments(t, foo_decl.docs, {"// foo doc"}) + expect_comments(t, foo_decl.comment, nil) + + foo := foo_decl.values[0].derived.(^ast.Struct_Type) + testing.expect_value(t, len(foo.fields.list), 4) + expect_comments(t, foo.fields.list[0].docs, {"// doc1"}) + expect_comments(t, foo.fields.list[0].comment, {"// c1"}) + expect_comments(t, foo.fields.list[1].docs, nil) + expect_comments(t, foo.fields.list[1].comment, {"// c2"}) + expect_comments(t, foo.fields.list[2].docs, {"// doc2", "// doc3"}) + expect_comments(t, foo.fields.list[2].comment, {"/* c3\nc4 */"}) + expect_comments(t, foo.fields.list[3].docs, nil) + expect_comments(t, foo.fields.list[3].comment, {"// c5"}) + + bar_decl := file.decls[1].derived.(^ast.Value_Decl) + expect_comments(t, bar_decl.docs, nil) + expect_comments(t, bar_decl.comment, {"// after"}) + + bar := bar_decl.values[0].derived.(^ast.Struct_Type) + testing.expect_value(t, len(bar.fields.list), 1) + expect_comments(t, bar.fields.list[0].docs, nil) + expect_comments(t, bar.fields.list[0].comment, {"/* c4 */"}) } @test