From b8976b52616dd47ef6ad26e93763f8875a2d91b4 Mon Sep 17 00:00:00 2001 From: substrate-bot Date: Fri, 13 Mar 2026 16:10:16 +0000 Subject: [PATCH 1/8] chore: bump lute to 0.1.0-nightly.20260312 --- README.md | 2 +- foreman.toml | 4 +- frontend/package.json | 1 + lua_helpers/typeAnnotations.lua | 103 +++++++++++++++++--------------- rokit.toml | 2 +- 5 files changed, 60 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 67993d8..b9eda7c 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ Use these formats for automatic changelog creation: ## 📋 Requirements - **VSCode** 1.74.0 or higher -- **Lute** (>= [0.1.0-nightly.20260130](https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20260130)) +- **Lute** (>= [0.1.0-nightly.20260312](https://github.com/luau-lang/lute/releases/tag/0.1.0-nightly.20260312)) - **Foreman** or **Rokit** (See installation instructions linked above) - **Node.js** 16+ (for development) diff --git a/foreman.toml b/foreman.toml index 83b09cb..73ab4f9 100644 --- a/foreman.toml +++ b/foreman.toml @@ -1,3 +1,3 @@ [tools] -lute = { source = "luau-lang/lute", version = "=0.1.0-nightly.20260130" } -stylua = { source = "JohnnyMorganz/StyLua", version = "2.0.2" } \ No newline at end of file +lute = { source = "luau-lang/lute", version = "=0.1.0-nightly.20260312" } +stylua = { source = "JohnnyMorganz/StyLua", version = "2.0.2" } diff --git a/frontend/package.json b/frontend/package.json index dcd53c1..1cf890d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", + "compile": "npm run build", "test": "react-scripts test --watchAll=false", "test:watch": "react-scripts test", "eject": "react-scripts eject" diff --git a/lua_helpers/typeAnnotations.lua b/lua_helpers/typeAnnotations.lua index 2749a21..c1be497 100644 --- a/lua_helpers/typeAnnotations.lua +++ b/lua_helpers/typeAnnotations.lua @@ -379,60 +379,67 @@ local function resolveAmbiguousKeys(nodeKey, node, parentNode, parentKey) return typeDefinitions.keys[nodeKey] end -local function annotateWithType(node, nodeKey, parent, parentKey) - if type(node) ~= "table" then - return node - end - - local astType = nil - - -- Priority 1: Check for 'kind' field for direct type mapping (local, attribute) - if node.kind == "local" and not node.tag then - astType = "AstLocal" - elseif node.kind == "attribute" then - astType = "AstAttribute" + local function annotateWithType(node, nodeKey, parent, parentKey) + if type(node) ~= "table" then + return node + end - -- Priority 2: Context-aware tag-based type resolution - elseif node.tag then - astType = resolveAmbiguousTags(node) + -- Lute AST nodes can be readonly in newer versions; avoid in-place mutation by + -- copying before annotating. + local out = {} + for key, value in pairs(node) do + out[key] = value + end - -- Priority 3: Check for 'kind' field for table items - elseif node.kind and node.istableitem then - astType = typeDefinitions.kinds[node.kind] - elseif node.kind and (node.key or node.indexeropen) then - -- Type table items - astType = typeDefinitions.kinds[node.kind] - - -- Priority 4: Check for istoken marker - elseif node.istoken then - astType = "Token" - - -- Priority 5: Key-based type resolution (fallback) - elseif nodeKey then - astType = resolveAmbiguousKeys(nodeKey, node, parent, parentKey) - - -- Priority 6: Generic token detection - elseif nodeKey and nodeKey:match("keyword$") then - -- Any property ending in "keyword" is probably a token - astType = "Token" - elseif nodeKey and (nodeKey:match("^open") or nodeKey:match("^close")) then - -- Properties like openparens, closebrace, etc. - astType = "Token" - end + local astType = nil + + -- Priority 1: Check for 'kind' field for direct type mapping (local, attribute) + if out.kind == "local" and not out.tag then + astType = "AstLocal" + elseif out.kind == "attribute" then + astType = "AstAttribute" + + -- Priority 2: Context-aware tag-based type resolution + elseif out.tag then + astType = resolveAmbiguousTags(out) + + -- Priority 3: Check for 'kind' field for table items + elseif out.kind and out.istableitem then + astType = typeDefinitions.kinds[out.kind] + elseif out.kind and (out.key or out.indexeropen) then + -- Type table items + astType = typeDefinitions.kinds[out.kind] + + -- Priority 4: Check for istoken marker + elseif out.istoken then + astType = "Token" + + -- Priority 5: Key-based type resolution (fallback) + elseif nodeKey then + astType = resolveAmbiguousKeys(nodeKey, out, parent, parentKey) + + -- Priority 6: Generic token detection + elseif nodeKey and nodeKey:match("keyword$") then + -- Any property ending in "keyword" is probably a token + astType = "Token" + elseif nodeKey and (nodeKey:match("^open") or nodeKey:match("^close")) then + -- Properties like openparens, closebrace, etc. + astType = "Token" + end - -- Add type annotation (skip arrays to avoid JSON encoding issues) - if astType and not (#node > 0) then - node._astType = astType - end + -- Add type annotation (skip arrays to avoid JSON encoding issues) + if astType and not (#out > 0) then + out._astType = astType + end - -- Recursively annotate children, passing key context - for key, value in pairs(node) do - if key ~= "_astType" then - node[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), node, nodeKey) + -- Recursively annotate children, passing key context + for key, value in pairs(out) do + if key ~= "_astType" then + out[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), out, nodeKey) + end end + return out end - return node -end return { annotateWithType = annotateWithType, diff --git a/rokit.toml b/rokit.toml index ab91290..423f218 100644 --- a/rokit.toml +++ b/rokit.toml @@ -1,3 +1,3 @@ [tools] -lute = "luau-lang/lute@0.1.0-nightly.20260130" +lute = "luau-lang/lute@0.1.0-nightly.20260312" stylua = "JohnnyMorganz/StyLua@2.0.2" From 3d8c129e13af845ac94267b35f2695b07413b5f0 Mon Sep 17 00:00:00 2001 From: substrate-bot Date: Fri, 13 Mar 2026 17:04:52 +0000 Subject: [PATCH 2/8] chore: sync internal AST type mirrors --- frontend/src/tests/TreeNode.test.tsx | 10 ++--- frontend/src/tests/astTypeHelpers.test.ts | 10 ++--- frontend/src/utils/astTypeDefinitions.ts | 55 +++++++++++++++-------- frontend/src/utils/astTypeHelpers.ts | 6 +-- lua_helpers/typeAnnotations.lua | 13 +++--- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/frontend/src/tests/TreeNode.test.tsx b/frontend/src/tests/TreeNode.test.tsx index 4f94bfd..eff52ac 100644 --- a/frontend/src/tests/TreeNode.test.tsx +++ b/frontend/src/tests/TreeNode.test.tsx @@ -259,9 +259,9 @@ describe("TreeNode", () => { test("fallback chain: _astType -> parentInferred -> array inference", () => { // Test actual fallback: parent type defines array property, mixed content with/without _astType const tableWithEntries = { - _astType: "AstExprTable", // Parent type that has `entries: { AstExprTableItem }` property + _astType: "AstExprTable", // Parent type that has `entries: { AstTableExprItem }` property entries: [ - { text: "filler" }, // No _astType - should infer AstExprTableItem from parent definition + { text: "filler" }, // No _astType - should infer AstTableExprItem from parent definition { _astType: "MySpecialType" }, // Has _astType - should use that directly ], }; @@ -275,14 +275,12 @@ describe("TreeNode", () => { // The entries array should get inferred type from AstExprTable definition const entriesQuery = getQueryableNode("root.entries", "nodeHeader"); expect( - entriesQuery.getByText(/type: { AstExprTableItem }/) + entriesQuery.getByText(/type: { AstTableExprItem }/) ).toBeInTheDocument(); // First item should infer type from parent array definition const item0Query = getQueryableNode("root.entries.0", "nodeHeader"); - expect( - item0Query.getByText(/type: AstExprTableItem/) - ).toBeInTheDocument(); + expect(item0Query.getByText(/type: AstTableExprItem/)).toBeInTheDocument(); // Second item should use its explicit _astType const item1Query = getQueryableNode("root.entries.1", "nodeHeader"); diff --git a/frontend/src/tests/astTypeHelpers.test.ts b/frontend/src/tests/astTypeHelpers.test.ts index f0cc014..7a5c9e4 100644 --- a/frontend/src/tests/astTypeHelpers.test.ts +++ b/frontend/src/tests/astTypeHelpers.test.ts @@ -24,12 +24,12 @@ describe("astTypeHelpers", () => { expect(getArrayType("statements")).toEqual(["{ AstStat }", ""]); expect(getArrayType("entries", [{ colon: ":", kind: "record" }])).toEqual([ - "{ AstTypeTableItem }", + "{ AstTableTypeItem }", "record", ]); expect(getArrayType("entries", [{ kind: "general" }])).toEqual([ - "{ AstExprTableItem }", + "{ AstTableExprItem }", "general", ]); @@ -105,14 +105,14 @@ describe("astTypeHelpers", () => { // leverages getArrayType expect(getTypeString([{}], "entries")).toEqual([ - "{ AstExprTableItem }", + "{ AstTableExprItem }", "", ]); // falls back on parentInferredType expect( - getTypeString({ kind: "record" }, "[0]", "AstExprTableItem") - ).toEqual(["AstExprTableItem", "record"]); + getTypeString({ kind: "record" }, "[0]", "AstTableExprItem") + ).toEqual(["AstTableExprItem", "record"]); }); test("getType", () => { diff --git a/frontend/src/utils/astTypeDefinitions.ts b/frontend/src/utils/astTypeDefinitions.ts index 48ee2e2..6402027 100644 --- a/frontend/src/utils/astTypeDefinitions.ts +++ b/frontend/src/utils/astTypeDefinitions.ts @@ -216,6 +216,24 @@ export const astTypeDefinitions: Record = { ], }, + AstExprInstantiate: { + properties: [ + { name: "location", type: "span" }, + { name: "kind", type: '"expr"' }, + { name: "tag", type: '"instantiate"' }, + { name: "expr", type: "AstExpr" }, + { name: "leftarrow1", type: "Token", generic: 'Token<"<">' }, + { name: "leftarrow2", type: "Token", generic: 'Token<"<">' }, + { + name: "typearguments", + type: "Punctuated", + generic: "Punctuated", + }, + { name: "rightarrow1", type: "Token", generic: 'Token<">">' }, + { name: "rightarrow2", type: "Token", generic: 'Token<">">' }, + ], + }, + AstExprIndexName: { properties: [ { name: "location", type: "span" }, @@ -304,7 +322,7 @@ export const astTypeDefinitions: Record = { ], }, - AstExprTableItemList: { + AstTableExprListItem: { properties: [ { name: "location", type: "span" }, { name: "kind", type: '"list"' }, @@ -319,7 +337,7 @@ export const astTypeDefinitions: Record = { ], }, - AstExprTableItemRecord: { + AstTableExprRecordItem: { properties: [ { name: "location", type: "span" }, { name: "kind", type: '"record"' }, @@ -336,7 +354,7 @@ export const astTypeDefinitions: Record = { ], }, - AstExprTableItemGeneral: { + AstTableExprGeneralItem: { properties: [ { name: "location", type: "span" }, { name: "kind", type: '"general"' }, @@ -355,11 +373,11 @@ export const astTypeDefinitions: Record = { ], }, - AstExprTableItem: { + AstTableExprItem: { unionMembers: [ - "AstExprTableItemList", - "AstExprTableItemRecord", - "AstExprTableItemGeneral", + "AstTableExprListItem", + "AstTableExprRecordItem", + "AstTableExprGeneralItem", ], }, @@ -369,7 +387,7 @@ export const astTypeDefinitions: Record = { { name: "kind", type: '"expr"' }, { name: "tag", type: '"table"' }, { name: "openbrace", type: "Token", generic: 'Token<"{">' }, - { name: "entries", type: "{ AstExprTableItem }" }, + { name: "entries", type: "{ AstTableExprItem }" }, { name: "closebrace", type: "Token", generic: 'Token<"}">' }, ], }, @@ -451,6 +469,7 @@ export const astTypeDefinitions: Record = { "AstExprGlobal", "AstExprVarargs", "AstExprCall", + "AstExprInstantiate", "AstExprIndexName", "AstExprIndexExpr", "AstExprFunction", @@ -935,7 +954,7 @@ export const astTypeDefinitions: Record = { ], }, - AstTypeTableItemIndexer: { + AstTableTypeItemIndexer: { properties: [ { name: "kind", type: '"indexer"' }, { @@ -958,7 +977,7 @@ export const astTypeDefinitions: Record = { ], }, - AstTypeTableItemStringProperty: { + AstTableTypeItemStringProperty: { properties: [ { name: "kind", type: '"stringproperty"' }, { @@ -981,7 +1000,7 @@ export const astTypeDefinitions: Record = { ], }, - AstTypeTableItemProperty: { + AstTableTypeItemProperty: { properties: [ { name: "kind", type: '"property"' }, { @@ -1002,11 +1021,11 @@ export const astTypeDefinitions: Record = { ], }, - AstTypeTableItem: { + AstTableTypeItem: { unionMembers: [ - "AstTypeTableItemIndexer", - "AstTypeTableItemStringProperty", - "AstTypeTableItemProperty", + "AstTableTypeItemIndexer", + "AstTableTypeItemStringProperty", + "AstTableTypeItemProperty", ], }, @@ -1016,12 +1035,12 @@ export const astTypeDefinitions: Record = { { name: "kind", type: '"type"' }, { name: "tag", type: '"table"' }, { name: "openbrace", type: "Token", generic: 'Token<"{">' }, - { name: "entries", type: "{ AstTypeTableItem }" }, + { name: "entries", type: "{ AstTableTypeItem }" }, { name: "closebrace", type: "Token", generic: 'Token<"}">' }, ], }, - AstTypeFunctionParameter: { + AstFunctionTypeParameter: { properties: [ { name: "location", type: "span" }, { name: "name", type: "Token", optional: true }, @@ -1063,7 +1082,7 @@ export const astTypeDefinitions: Record = { { name: "parameters", type: "Punctuated", - generic: "Punctuated", + generic: "Punctuated", }, { name: "vararg", type: "AstTypePack", optional: true }, { name: "closeparens", type: "Token", generic: 'Token<")">' }, diff --git a/frontend/src/utils/astTypeHelpers.ts b/frontend/src/utils/astTypeHelpers.ts index 989e8a0..4c33712 100644 --- a/frontend/src/utils/astTypeHelpers.ts +++ b/frontend/src/utils/astTypeHelpers.ts @@ -79,10 +79,10 @@ export function getAllNodeKeys(): string[] { const resolveEntriesType = (value: any[]): string => { if (value[0] && value[0].colon) { - return "{ AstTypeTableItem }"; + return "{ AstTableTypeItem }"; } - return "{ AstExprTableItem }"; + return "{ AstTableExprItem }"; }; const resolveEntriesKind = (value: any[]): string => { @@ -187,4 +187,4 @@ export const getChildPropertyDefinition = ( (prop) => prop.name === childKey ); } -}; \ No newline at end of file +}; diff --git a/lua_helpers/typeAnnotations.lua b/lua_helpers/typeAnnotations.lua index c1be497..7f81f9e 100644 --- a/lua_helpers/typeAnnotations.lua +++ b/lua_helpers/typeAnnotations.lua @@ -21,6 +21,7 @@ local typeDefinitions = { ["global"] = "AstExprGlobal", ["vararg"] = "AstExprVarargs", ["call"] = "AstExprCall", + ["instantiate"] = "AstExprInstantiate", ["indexname"] = "AstExprIndexName", ["index"] = "AstExprIndexExpr", ["unary"] = "AstExprUnary", @@ -171,14 +172,14 @@ local typeDefinitions = { -- Kind-based mappings for table items and other kinded structures kinds = { -- Expression table items (istableitem: true) - ["record"] = "AstExprTableItemRecord", - ["general"] = "AstExprTableItemGeneral", - ["list"] = "AstExprTableItemList", + ["record"] = "AstTableExprRecordItem", + ["general"] = "AstTableExprGeneralItem", + ["list"] = "AstTableExprListItem", -- Type table items - ["property"] = "AstTypeTableItemProperty", - ["indexer"] = "AstTypeTableItemIndexer", - ["stringproperty"] = "AstTypeTableItemStringProperty", + ["property"] = "AstTableTypeItemProperty", + ["indexer"] = "AstTableTypeItemIndexer", + ["stringproperty"] = "AstTableTypeItemStringProperty", -- AstLocal ["local"] = "AstLocal", From 37b54d3cbe5a37eb7064637a3eef2d2df5314f36 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Mar 2026 17:10:29 +0000 Subject: [PATCH 3/8] Add changelog file for PR #68 --- .changes/68-feat.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/68-feat.md diff --git a/.changes/68-feat.md b/.changes/68-feat.md new file mode 100644 index 0000000..6d72833 --- /dev/null +++ b/.changes/68-feat.md @@ -0,0 +1 @@ +compatibility w/ lute to 0.1.0-nightly.20260312 From 0e8616b815f95cdb77a9d0b964d79fd42fa7c3bb Mon Sep 17 00:00:00 2001 From: Wyatt McCarthy <115899870+wmccrthy@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:13:55 -0700 Subject: [PATCH 4/8] Update 68-feat.md --- .changes/68-feat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/68-feat.md b/.changes/68-feat.md index 6d72833..0bcd1a6 100644 --- a/.changes/68-feat.md +++ b/.changes/68-feat.md @@ -1 +1 @@ -compatibility w/ lute to 0.1.0-nightly.20260312 +Make compatible w/ lute to 0.1.0-nightly.20260312 From f6317aedf9d9d6f878b27206db8c1df3f39ad345 Mon Sep 17 00:00:00 2001 From: Wyatt McCarthy Date: Sun, 15 Mar 2026 13:45:49 -0700 Subject: [PATCH 5/8] nit corrections --- lua_helpers/typeAnnotations.lua | 41 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/lua_helpers/typeAnnotations.lua b/lua_helpers/typeAnnotations.lua index 7f81f9e..29e3a0b 100644 --- a/lua_helpers/typeAnnotations.lua +++ b/lua_helpers/typeAnnotations.lua @@ -190,7 +190,7 @@ local typeDefinitions = { } -- Context-aware type resolution for ambiguous tags -local function resolveAmbiguousTags(node) +local function resolveAmbiguousTags(node): string? local tag = node.tag if not tag then return nil @@ -280,7 +280,7 @@ local function resolveAmbiguousTags(node) return typeDefinitions.tags[tag] end -local function resolveAmbiguousKeys(nodeKey, node, parentNode, parentKey) +local function resolveAmbiguousKeys(nodeKey, node, parentNode): string? -- Handle 'kind' field specially - it's a discriminator, not a type if nodeKey == "kind" then return nil -- Don't annotate the kind field itself @@ -387,37 +387,34 @@ end -- Lute AST nodes can be readonly in newer versions; avoid in-place mutation by -- copying before annotating. - local out = {} - for key, value in pairs(node) do - out[key] = value - end + local annotatedNode = table.clone(node) - local astType = nil + local astType: string? -- Priority 1: Check for 'kind' field for direct type mapping (local, attribute) - if out.kind == "local" and not out.tag then + if annotatedNode.kind == "local" and not annotatedNode.tag then astType = "AstLocal" - elseif out.kind == "attribute" then + elseif annotatedNode.kind == "attribute" then astType = "AstAttribute" -- Priority 2: Context-aware tag-based type resolution - elseif out.tag then - astType = resolveAmbiguousTags(out) + elseif annotatedNode.tag then + astType = resolveAmbiguousTags(annotatedNode) -- Priority 3: Check for 'kind' field for table items - elseif out.kind and out.istableitem then - astType = typeDefinitions.kinds[out.kind] - elseif out.kind and (out.key or out.indexeropen) then + elseif annotatedNode.kind and annotatedNode.istableitem then + astType = typeDefinitions.kinds[annotatedNode.kind] + elseif annotatedNode.kind and (annotatedNode.key or annotatedNode.indexeropen) then -- Type table items - astType = typeDefinitions.kinds[out.kind] + astType = typeDefinitions.kinds[annotatedNode.kind] -- Priority 4: Check for istoken marker - elseif out.istoken then + elseif annotatedNode.istoken then astType = "Token" -- Priority 5: Key-based type resolution (fallback) elseif nodeKey then - astType = resolveAmbiguousKeys(nodeKey, out, parent, parentKey) + astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent, parentKey) -- Priority 6: Generic token detection elseif nodeKey and nodeKey:match("keyword$") then @@ -429,17 +426,17 @@ end end -- Add type annotation (skip arrays to avoid JSON encoding issues) - if astType and not (#out > 0) then - out._astType = astType + if astType and not (#annotatedNode > 0) then + annotatedNode._astType = astType end -- Recursively annotate children, passing key context - for key, value in pairs(out) do + for key, value in annotatedNode do if key ~= "_astType" then - out[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), out, nodeKey) + annotatedNode[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), annotatedNode, nodeKey) end end - return out + return annotatedNode end return { From 3f37442e3322ae01683c969f73a117f12551fa32 Mon Sep 17 00:00:00 2001 From: Wyatt McCarthy Date: Sun, 15 Mar 2026 13:51:39 -0700 Subject: [PATCH 6/8] stylua + make sure tests cover ExprInstantiate --- lua_helpers/typeAnnotations.lua | 83 ++++++++++--------- lua_tests/helpers/astJsonToCodeHelpers.lua | 1 + .../helpers/typeAnnotationTestHelpers.lua | 5 ++ 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/lua_helpers/typeAnnotations.lua b/lua_helpers/typeAnnotations.lua index 29e3a0b..93c633b 100644 --- a/lua_helpers/typeAnnotations.lua +++ b/lua_helpers/typeAnnotations.lua @@ -380,64 +380,65 @@ local function resolveAmbiguousKeys(nodeKey, node, parentNode): string? return typeDefinitions.keys[nodeKey] end - local function annotateWithType(node, nodeKey, parent, parentKey) - if type(node) ~= "table" then - return node - end +local function annotateWithType(node, nodeKey, parent, parentKey) + if type(node) ~= "table" then + return node + end - -- Lute AST nodes can be readonly in newer versions; avoid in-place mutation by - -- copying before annotating. - local annotatedNode = table.clone(node) + -- Lute AST nodes can be readonly in newer versions; avoid in-place mutation by + -- copying before annotating. + local annotatedNode = table.clone(node) - local astType: string? + local astType: string? - -- Priority 1: Check for 'kind' field for direct type mapping (local, attribute) - if annotatedNode.kind == "local" and not annotatedNode.tag then - astType = "AstLocal" - elseif annotatedNode.kind == "attribute" then - astType = "AstAttribute" + -- Priority 1: Check for 'kind' field for direct type mapping (local, attribute) + if annotatedNode.kind == "local" and not annotatedNode.tag then + astType = "AstLocal" + elseif annotatedNode.kind == "attribute" then + astType = "AstAttribute" -- Priority 2: Context-aware tag-based type resolution - elseif annotatedNode.tag then - astType = resolveAmbiguousTags(annotatedNode) + elseif annotatedNode.tag then + astType = resolveAmbiguousTags(annotatedNode) -- Priority 3: Check for 'kind' field for table items - elseif annotatedNode.kind and annotatedNode.istableitem then - astType = typeDefinitions.kinds[annotatedNode.kind] - elseif annotatedNode.kind and (annotatedNode.key or annotatedNode.indexeropen) then - -- Type table items - astType = typeDefinitions.kinds[annotatedNode.kind] + elseif annotatedNode.kind and annotatedNode.istableitem then + astType = typeDefinitions.kinds[annotatedNode.kind] + elseif annotatedNode.kind and (annotatedNode.key or annotatedNode.indexeropen) then + -- Type table items + astType = typeDefinitions.kinds[annotatedNode.kind] -- Priority 4: Check for istoken marker - elseif annotatedNode.istoken then - astType = "Token" + elseif annotatedNode.istoken then + astType = "Token" -- Priority 5: Key-based type resolution (fallback) - elseif nodeKey then - astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent, parentKey) + elseif nodeKey then + astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent, parentKey) -- Priority 6: Generic token detection - elseif nodeKey and nodeKey:match("keyword$") then - -- Any property ending in "keyword" is probably a token - astType = "Token" - elseif nodeKey and (nodeKey:match("^open") or nodeKey:match("^close")) then - -- Properties like openparens, closebrace, etc. - astType = "Token" - end + elseif nodeKey and nodeKey:match("keyword$") then + -- Any property ending in "keyword" is probably a token + astType = "Token" + elseif nodeKey and (nodeKey:match("^open") or nodeKey:match("^close")) then + -- Properties like openparens, closebrace, etc. + astType = "Token" + end - -- Add type annotation (skip arrays to avoid JSON encoding issues) - if astType and not (#annotatedNode > 0) then - annotatedNode._astType = astType - end + -- Add type annotation (skip arrays to avoid JSON encoding issues) + if astType and not (#annotatedNode > 0) then + annotatedNode._astType = astType + end - -- Recursively annotate children, passing key context - for key, value in annotatedNode do - if key ~= "_astType" then - annotatedNode[key] = annotateWithType(value, if typeof(key) == "string" then key else tostring(key), annotatedNode, nodeKey) - end + -- Recursively annotate children, passing key context + for key, value in annotatedNode do + if key ~= "_astType" then + annotatedNode[key] = + annotateWithType(value, if typeof(key) == "string" then key else tostring(key), annotatedNode, nodeKey) end - return annotatedNode end + return annotatedNode +end return { annotateWithType = annotateWithType, diff --git a/lua_tests/helpers/astJsonToCodeHelpers.lua b/lua_tests/helpers/astJsonToCodeHelpers.lua index 10bf74b..ef64b62 100644 --- a/lua_tests/helpers/astJsonToCodeHelpers.lua +++ b/lua_tests/helpers/astJsonToCodeHelpers.lua @@ -99,6 +99,7 @@ local cases = { "type Generic = { data: T }", "type Props = { titleText: string, avatarThumbnail: string, avatarThumbnailDisplayHeight: number, bodyText: string?, primaryButtonText: string, onPrimaryButtonActivated: () -> ()?, secondaryButtonText: string?, onSecondaryButtonActivated: () -> ()?, closeCentralOverlayThunk: () -> any? }", "local nil_val = nil", + "a<>", -- Edge cases that test printASTNode fallback behavior "local x", "local y: number", diff --git a/lua_tests/helpers/typeAnnotationTestHelpers.lua b/lua_tests/helpers/typeAnnotationTestHelpers.lua index 98a22c7..2dcd2fa 100644 --- a/lua_tests/helpers/typeAnnotationTestHelpers.lua +++ b/lua_tests/helpers/typeAnnotationTestHelpers.lua @@ -212,6 +212,11 @@ typeAnnotationVisitor.visitExprTable = function(node: luau.AstExprTable) return true end +typeAnnotationVisitor.visitExprInstantiate = function(node: luau.AstExprInstantiate) + verifyOutput(node, "visitTable", node._astType == "AstExprInstantiate") + return true +end + -- Type visitors typeAnnotationVisitor.visitTypeReference = function(node: luau.AstTypeReference) verifyOutput(node, "visitTypeReference", node._astType == "AstTypeReference") From 37083b633e0e51532e74fc8d9ea0175b238e5103 Mon Sep 17 00:00:00 2001 From: Wyatt McCarthy Date: Sun, 15 Mar 2026 13:55:40 -0700 Subject: [PATCH 7/8] fix tests --- lua_tests/helpers/astJsonToCodeHelpers.lua | 2 +- lua_tests/helpers/typeAnnotationTestHelpers.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lua_tests/helpers/astJsonToCodeHelpers.lua b/lua_tests/helpers/astJsonToCodeHelpers.lua index ef64b62..9126c30 100644 --- a/lua_tests/helpers/astJsonToCodeHelpers.lua +++ b/lua_tests/helpers/astJsonToCodeHelpers.lua @@ -99,7 +99,7 @@ local cases = { "type Generic = { data: T }", "type Props = { titleText: string, avatarThumbnail: string, avatarThumbnailDisplayHeight: number, bodyText: string?, primaryButtonText: string, onPrimaryButtonActivated: () -> ()?, secondaryButtonText: string?, onSecondaryButtonActivated: () -> ()?, closeCentralOverlayThunk: () -> any? }", "local nil_val = nil", - "a<>", + "local b = a<>", -- Edge cases that test printASTNode fallback behavior "local x", "local y: number", diff --git a/lua_tests/helpers/typeAnnotationTestHelpers.lua b/lua_tests/helpers/typeAnnotationTestHelpers.lua index 2dcd2fa..6d2082f 100644 --- a/lua_tests/helpers/typeAnnotationTestHelpers.lua +++ b/lua_tests/helpers/typeAnnotationTestHelpers.lua @@ -213,7 +213,7 @@ typeAnnotationVisitor.visitExprTable = function(node: luau.AstExprTable) end typeAnnotationVisitor.visitExprInstantiate = function(node: luau.AstExprInstantiate) - verifyOutput(node, "visitTable", node._astType == "AstExprInstantiate") + verifyOutput(node, "visitExprInstantiate", node._astType == "AstExprInstantiate") return true end From 5f7f83b7869b30d210d58ccfa53334e72715fc2c Mon Sep 17 00:00:00 2001 From: Wyatt McCarthy Date: Sun, 15 Mar 2026 13:58:08 -0700 Subject: [PATCH 8/8] nits --- lua_helpers/typeAnnotations.lua | 6 +++--- lua_tests/typeAnnotations.spec.lua | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua_helpers/typeAnnotations.lua b/lua_helpers/typeAnnotations.lua index 93c633b..f70113d 100644 --- a/lua_helpers/typeAnnotations.lua +++ b/lua_helpers/typeAnnotations.lua @@ -280,7 +280,7 @@ local function resolveAmbiguousTags(node): string? return typeDefinitions.tags[tag] end -local function resolveAmbiguousKeys(nodeKey, node, parentNode): string? +local function resolveAmbiguousKeys(nodeKey, node, parentNode: any?): string? -- Handle 'kind' field specially - it's a discriminator, not a type if nodeKey == "kind" then return nil -- Don't annotate the kind field itself @@ -380,7 +380,7 @@ local function resolveAmbiguousKeys(nodeKey, node, parentNode): string? return typeDefinitions.keys[nodeKey] end -local function annotateWithType(node, nodeKey, parent, parentKey) +local function annotateWithType(node, nodeKey: (string | number)?, parent: any?, parentKey: (string | number)?) if type(node) ~= "table" then return node end @@ -414,7 +414,7 @@ local function annotateWithType(node, nodeKey, parent, parentKey) -- Priority 5: Key-based type resolution (fallback) elseif nodeKey then - astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent, parentKey) + astType = resolveAmbiguousKeys(nodeKey, annotatedNode, parent) -- Priority 6: Generic token detection elseif nodeKey and nodeKey:match("keyword$") then diff --git a/lua_tests/typeAnnotations.spec.lua b/lua_tests/typeAnnotations.spec.lua index b954d6a..8e005d1 100644 --- a/lua_tests/typeAnnotations.spec.lua +++ b/lua_tests/typeAnnotations.spec.lua @@ -8,7 +8,7 @@ local typeAnnotationHelpers = require("./helpers/typeAnnotationTestHelpers") local function annotateWithType_test() -- parse and annotate src code snippet (with large coverage of syntax constructs) - local testAST = annotateWithType(parser.parseblock(typeAnnotationHelpers.testSrc)) + local testAST = annotateWithType(parser.parseblock(typeAnnotationHelpers.testSrc) :: any) -- visit annotated tree to check for correct type assignment (using typeAnnotationVisitor) local typeAnnotationChecker = typeAnnotationHelpers.typeAnnotationVisitor visitor.visit(testAST, typeAnnotationChecker)