From f265318700e22d90f8e1e9beb8abe3a192802cc2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 5 Oct 2025 12:56:25 -0400 Subject: [PATCH] Add nested types --- src/parse/AST.zig | 15 ++ src/parse/Parser.zig | 6 +- ...ominal_associated_with_final_expression.md | 7 +- .../nominal/nominal_deeply_nested_types.md | 143 ++++++++++++++++++ .../snapshots/nominal/nominal_nested_types.md | 99 ++++++++++++ ...al_type_with_associated_multi_statement.md | 12 +- ...l_type_with_associated_single_statement.md | 6 +- .../nominal_type_with_empty_associated.md | 3 +- .../nominal/type_alias_with_associated.md | 6 +- 9 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 test/snapshots/nominal/nominal_deeply_nested_types.md create mode 100644 test/snapshots/nominal/nominal_nested_types.md diff --git a/src/parse/AST.zig b/src/parse/AST.zig index bfd2197a20..bf00ac8db8 100644 --- a/src/parse/AST.zig +++ b/src/parse/AST.zig @@ -1014,6 +1014,21 @@ pub const Statement = union(enum) { try ast.store.getTypeAnno(a.anno).pushToSExprTree(gpa, env, ast, tree); + // Add associated block if present + if (a.associated) |assoc| { + const assoc_begin = tree.beginNode(); + try tree.pushStaticAtom("associated"); + try ast.appendRegionInfoToSexprTree(env, tree, assoc.region); + const assoc_attrs = tree.beginNode(); + + for (ast.store.statementSlice(assoc.statements)) |stmt_idx| { + const stmt = ast.store.getStatement(stmt_idx); + try stmt.pushToSExprTree(gpa, env, ast, tree); + } + + try tree.endNode(assoc_begin, assoc_attrs); + } + try tree.endNode(begin, attrs); }, .crash => |a| { diff --git a/src/parse/Parser.zig b/src/parse/Parser.zig index 1cbd0137a6..3ef8835034 100644 --- a/src/parse/Parser.zig +++ b/src/parse/Parser.zig @@ -930,7 +930,7 @@ pub fn parseExposedItem(self: *Parser) Error!AST.ExposedItem.Idx { } } -const StatementType = enum { top_level, in_body }; +const StatementType = enum { top_level, in_body, in_associated_block }; /// Parse a top level roc statement /// @@ -1170,7 +1170,7 @@ fn parseStmtByType(self: *Parser, statementType: StatementType) Error!AST.Statem // Type Annotation (e.g. `Foo a : (a,a)`) .UpperIdent => { const start = self.pos; - if (statementType == .top_level) { + if (statementType == .top_level or statementType == .in_associated_block) { const header = try self.parseTypeHeader(); if (self.peek() != .OpColon and self.peek() != .OpColonEqual) { // Point to the unexpected token (e.g., "U8" in "List U8") @@ -2969,7 +2969,7 @@ pub fn parseStatementOnlyBlock(self: *Parser, start: u32) Error!AST.Associated { while (self.peek() != .EndOfFile and self.peek() != .CloseCurly) { const statement_pos = self.pos; - const statement = try self.parseStmt(); + const statement = try self.parseStmtByType(.in_associated_block); // Check if this is an expression statement (the last statement must not be an expression) const stmt = self.store.getStatement(statement); diff --git a/test/snapshots/nominal/nominal_associated_with_final_expression.md b/test/snapshots/nominal/nominal_associated_with_final_expression.md index 3a1de665ab..ad14c34095 100644 --- a/test/snapshots/nominal/nominal_associated_with_final_expression.md +++ b/test/snapshots/nominal/nominal_associated_with_final_expression.md @@ -41,7 +41,12 @@ EndOfFile(3:1-3:1), (tags (ty @1.9-1.10 (name "A")) (ty @1.12-1.13 (name "B")) - (ty @1.15-1.16 (name "C"))))))) + (ty @1.15-1.16 (name "C")))) + (associated @1.18-2.4 + (s-decl @1.20-1.25 + (p-ident @1.20-1.21 (raw "x")) + (e-int @1.24-1.25 (raw "5"))) + (e-ident @2.1-2.2 (raw "x")))))) ~~~ # FORMATTED ~~~roc diff --git a/test/snapshots/nominal/nominal_deeply_nested_types.md b/test/snapshots/nominal/nominal_deeply_nested_types.md new file mode 100644 index 0000000000..bf3e340ba0 --- /dev/null +++ b/test/snapshots/nominal/nominal_deeply_nested_types.md @@ -0,0 +1,143 @@ +# META +~~~ini +description=Deeply nested types (3+ levels) in associated blocks +type=file +~~~ +# SOURCE +~~~roc +Foo := [Whatever].{ + Bar := [Something].{ + Baz := [Else].{ + Qux := [Deep].{ + w = 1 + } + z = 2 + } + y = 3 + } + x = 4 +} +~~~ +# EXPECTED +TYPE MODULE MISSING MATCHING TYPE - nominal_deeply_nested_types.md:1:1:12:2 +# PROBLEMS +**TYPE MODULE MISSING MATCHING TYPE** +Type modules must have a type declaration matching the module name. + +This file is named `nominal_deeply_nested_types`.roc, but no top-level type declaration named `nominal_deeply_nested_types` was found. + +Add either: +`nominal_deeply_nested_types := ...` (nominal type) +or: +`nominal_deeply_nested_types : ...` (type alias) +**nominal_deeply_nested_types.md:1:1:12:2:** +```roc +Foo := [Whatever].{ + Bar := [Something].{ + Baz := [Else].{ + Qux := [Deep].{ + w = 1 + } + z = 2 + } + y = 3 + } + x = 4 +} +``` + + +# TOKENS +~~~zig +UpperIdent(1:1-1:4),OpColonEqual(1:5-1:7),OpenSquare(1:8-1:9),UpperIdent(1:9-1:17),CloseSquare(1:17-1:18),Dot(1:18-1:19),OpenCurly(1:19-1:20), +UpperIdent(2:5-2:8),OpColonEqual(2:9-2:11),OpenSquare(2:12-2:13),UpperIdent(2:13-2:22),CloseSquare(2:22-2:23),Dot(2:23-2:24),OpenCurly(2:24-2:25), +UpperIdent(3:9-3:12),OpColonEqual(3:13-3:15),OpenSquare(3:16-3:17),UpperIdent(3:17-3:21),CloseSquare(3:21-3:22),Dot(3:22-3:23),OpenCurly(3:23-3:24), +UpperIdent(4:13-4:16),OpColonEqual(4:17-4:19),OpenSquare(4:20-4:21),UpperIdent(4:21-4:25),CloseSquare(4:25-4:26),Dot(4:26-4:27),OpenCurly(4:27-4:28), +LowerIdent(5:17-5:18),OpAssign(5:19-5:20),Int(5:21-5:22), +CloseCurly(6:13-6:14), +LowerIdent(7:13-7:14),OpAssign(7:15-7:16),Int(7:17-7:18), +CloseCurly(8:9-8:10), +LowerIdent(9:9-9:10),OpAssign(9:11-9:12),Int(9:13-9:14), +CloseCurly(10:5-10:6), +LowerIdent(11:5-11:6),OpAssign(11:7-11:8),Int(11:9-11:10), +CloseCurly(12:1-12:2), +EndOfFile(13:1-13:1), +~~~ +# PARSE +~~~clojure +(file @1.1-12.2 + (type-module @1.1-1.4) + (statements + (s-type-decl @1.1-12.2 + (header @1.1-1.4 (name "Foo") + (args)) + (ty-tag-union @1.8-1.18 + (tags + (ty @1.9-1.17 (name "Whatever")))) + (associated @1.19-12.2 + (s-type-decl @2.5-10.6 + (header @2.5-2.8 (name "Bar") + (args)) + (ty-tag-union @2.12-2.23 + (tags + (ty @2.13-2.22 (name "Something")))) + (associated @2.24-10.6 + (s-type-decl @3.9-8.10 + (header @3.9-3.12 (name "Baz") + (args)) + (ty-tag-union @3.16-3.22 + (tags + (ty @3.17-3.21 (name "Else")))) + (associated @3.23-8.10 + (s-type-decl @4.13-6.14 + (header @4.13-4.16 (name "Qux") + (args)) + (ty-tag-union @4.20-4.26 + (tags + (ty @4.21-4.25 (name "Deep")))) + (associated @4.27-6.14 + (s-decl @5.17-5.22 + (p-ident @5.17-5.18 (raw "w")) + (e-int @5.21-5.22 (raw "1"))))) + (s-decl @7.13-7.18 + (p-ident @7.13-7.14 (raw "z")) + (e-int @7.17-7.18 (raw "2"))))) + (s-decl @9.9-9.14 + (p-ident @9.9-9.10 (raw "y")) + (e-int @9.13-9.14 (raw "3"))))) + (s-decl @11.5-11.10 + (p-ident @11.5-11.6 (raw "x")) + (e-int @11.9-11.10 (raw "4"))))))) +~~~ +# FORMATTED +~~~roc +Foo := [Whatever].{ + Bar := [Something].{ + Baz := [Else].{ + Qux := [Deep].{ + w = 1 + } + z = 2 + } + y = 3 + } + x = 4 +} +~~~ +# CANONICALIZE +~~~clojure +(can-ir + (s-nominal-decl @1.1-12.2 + (ty-header @1.1-1.4 (name "Foo")) + (ty-tag-union @1.8-1.18 + (ty-tag-name @1.9-1.17 (name "Whatever"))))) +~~~ +# TYPES +~~~clojure +(inferred-types + (defs) + (type_decls + (nominal @1.1-12.2 (type "Foo") + (ty-header @1.1-1.4 (name "Foo")))) + (expressions)) +~~~ diff --git a/test/snapshots/nominal/nominal_nested_types.md b/test/snapshots/nominal/nominal_nested_types.md new file mode 100644 index 0000000000..75429af39c --- /dev/null +++ b/test/snapshots/nominal/nominal_nested_types.md @@ -0,0 +1,99 @@ +# META +~~~ini +description=Nested types in associated blocks +type=file +~~~ +# SOURCE +~~~roc +Foo := [Whatever].{ + Bar := [Something].{ + y = 6 + } + x = 5 +} +~~~ +# EXPECTED +TYPE MODULE MISSING MATCHING TYPE - nominal_nested_types.md:1:1:6:2 +# PROBLEMS +**TYPE MODULE MISSING MATCHING TYPE** +Type modules must have a type declaration matching the module name. + +This file is named `nominal_nested_types`.roc, but no top-level type declaration named `nominal_nested_types` was found. + +Add either: +`nominal_nested_types := ...` (nominal type) +or: +`nominal_nested_types : ...` (type alias) +**nominal_nested_types.md:1:1:6:2:** +```roc +Foo := [Whatever].{ + Bar := [Something].{ + y = 6 + } + x = 5 +} +``` + + +# TOKENS +~~~zig +UpperIdent(1:1-1:4),OpColonEqual(1:5-1:7),OpenSquare(1:8-1:9),UpperIdent(1:9-1:17),CloseSquare(1:17-1:18),Dot(1:18-1:19),OpenCurly(1:19-1:20), +UpperIdent(2:5-2:8),OpColonEqual(2:9-2:11),OpenSquare(2:12-2:13),UpperIdent(2:13-2:22),CloseSquare(2:22-2:23),Dot(2:23-2:24),OpenCurly(2:24-2:25), +LowerIdent(3:9-3:10),OpAssign(3:11-3:12),Int(3:13-3:14), +CloseCurly(4:5-4:6), +LowerIdent(5:5-5:6),OpAssign(5:7-5:8),Int(5:9-5:10), +CloseCurly(6:1-6:2), +EndOfFile(7:1-7:1), +~~~ +# PARSE +~~~clojure +(file @1.1-6.2 + (type-module @1.1-1.4) + (statements + (s-type-decl @1.1-6.2 + (header @1.1-1.4 (name "Foo") + (args)) + (ty-tag-union @1.8-1.18 + (tags + (ty @1.9-1.17 (name "Whatever")))) + (associated @1.19-6.2 + (s-type-decl @2.5-4.6 + (header @2.5-2.8 (name "Bar") + (args)) + (ty-tag-union @2.12-2.23 + (tags + (ty @2.13-2.22 (name "Something")))) + (associated @2.24-4.6 + (s-decl @3.9-3.14 + (p-ident @3.9-3.10 (raw "y")) + (e-int @3.13-3.14 (raw "6"))))) + (s-decl @5.5-5.10 + (p-ident @5.5-5.6 (raw "x")) + (e-int @5.9-5.10 (raw "5"))))))) +~~~ +# FORMATTED +~~~roc +Foo := [Whatever].{ + Bar := [Something].{ + y = 6 + } + x = 5 +} +~~~ +# CANONICALIZE +~~~clojure +(can-ir + (s-nominal-decl @1.1-6.2 + (ty-header @1.1-1.4 (name "Foo")) + (ty-tag-union @1.8-1.18 + (ty-tag-name @1.9-1.17 (name "Whatever"))))) +~~~ +# TYPES +~~~clojure +(inferred-types + (defs) + (type_decls + (nominal @1.1-6.2 (type "Foo") + (ty-header @1.1-1.4 (name "Foo")))) + (expressions)) +~~~ diff --git a/test/snapshots/nominal/nominal_type_with_associated_multi_statement.md b/test/snapshots/nominal/nominal_type_with_associated_multi_statement.md index d5da90e5b7..2e53dd28b5 100644 --- a/test/snapshots/nominal/nominal_type_with_associated_multi_statement.md +++ b/test/snapshots/nominal/nominal_type_with_associated_multi_statement.md @@ -36,7 +36,17 @@ EndOfFile(6:1-6:1), (tags (ty @1.9-1.10 (name "A")) (ty @1.12-1.13 (name "B")) - (ty @1.15-1.16 (name "C"))))))) + (ty @1.15-1.16 (name "C")))) + (associated @1.18-5.2 + (s-decl @2.5-2.10 + (p-ident @2.5-2.6 (raw "x")) + (e-int @2.9-2.10 (raw "5"))) + (s-decl @3.5-3.11 + (p-ident @3.5-3.6 (raw "y")) + (e-int @3.9-3.11 (raw "10"))) + (s-decl @4.5-4.11 + (p-ident @4.5-4.6 (raw "z")) + (e-int @4.9-4.11 (raw "15"))))))) ~~~ # FORMATTED ~~~roc diff --git a/test/snapshots/nominal/nominal_type_with_associated_single_statement.md b/test/snapshots/nominal/nominal_type_with_associated_single_statement.md index 6560001c53..14dcbe31bd 100644 --- a/test/snapshots/nominal/nominal_type_with_associated_single_statement.md +++ b/test/snapshots/nominal/nominal_type_with_associated_single_statement.md @@ -28,7 +28,11 @@ EndOfFile(2:1-2:1), (tags (ty @1.9-1.10 (name "A")) (ty @1.12-1.13 (name "B")) - (ty @1.15-1.16 (name "C"))))))) + (ty @1.15-1.16 (name "C")))) + (associated @1.18-1.27 + (s-decl @1.20-1.25 + (p-ident @1.20-1.21 (raw "x")) + (e-int @1.24-1.25 (raw "5"))))))) ~~~ # FORMATTED ~~~roc diff --git a/test/snapshots/nominal/nominal_type_with_empty_associated.md b/test/snapshots/nominal/nominal_type_with_empty_associated.md index 2e398eb3a2..1a84d2cba2 100644 --- a/test/snapshots/nominal/nominal_type_with_empty_associated.md +++ b/test/snapshots/nominal/nominal_type_with_empty_associated.md @@ -28,7 +28,8 @@ EndOfFile(2:1-2:1), (tags (ty @1.9-1.10 (name "A")) (ty @1.12-1.13 (name "B")) - (ty @1.15-1.16 (name "C"))))))) + (ty @1.15-1.16 (name "C")))) + (associated @1.18-1.20)))) ~~~ # FORMATTED ~~~roc diff --git a/test/snapshots/nominal/type_alias_with_associated.md b/test/snapshots/nominal/type_alias_with_associated.md index 301b9380ef..54d655fe7b 100644 --- a/test/snapshots/nominal/type_alias_with_associated.md +++ b/test/snapshots/nominal/type_alias_with_associated.md @@ -39,7 +39,11 @@ EndOfFile(2:1-2:1), (tags (ty @1.8-1.9 (name "A")) (ty @1.11-1.12 (name "B")) - (ty @1.14-1.15 (name "C"))))))) + (ty @1.14-1.15 (name "C")))) + (associated @1.17-1.26 + (s-decl @1.19-1.24 + (p-ident @1.19-1.20 (raw "x")) + (e-int @1.23-1.24 (raw "5"))))))) ~~~ # FORMATTED ~~~roc