Skip to content

perf: fix O(n²) descendant traversal + close #33 (disallow Luau keywords)#59

Merged
R-unic merged 6 commits into
masterfrom
perf/descendants-and-luau-keywords
Jul 24, 2026
Merged

perf: fix O(n²) descendant traversal + close #33 (disallow Luau keywords)#59
R-unic merged 6 commits into
masterfrom
perf/descendants-and-luau-keywords

Conversation

@R-unic

@R-unic R-unic commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

Continued hot-path cleanup, plus GitHub issue #33.

Perf

  • Node.GetDescendants() rebuilt/copied at every level of recursion (O(n²) for deep or chain-like subtrees) — rewritten as a single-pass BFS traversal.
  • TypeChecker.GetReturnType walked the entire subtree of a function body (including nested function bodies) via GetDescendants<Return>(), then filtered back down via an ancestor check — replaced with a bounded traversal that stops at nested FunctionDeclaration boundaries.

Bugs found and fixed along the way

  • FlowAnalyzer.Analyze() had stopped invoking the actual analysis pass entirely (a dropped BindState(...) call), so every flow-dependent diagnostic — unreachable code, use of uninitialized/maybe-uninitialized variables, assignment to immutable bindings — had silently stopped firing.
  • FlowAnalyzer had no case for EnumDeclaration (unlike InterfaceDeclaration/EventDeclaration, which both mark their own symbol as initialized), so referencing an enum after declaring it incorrectly tripped "use of uninitialized variable". No existing test exercised an enum through the full pipeline, so this went unnoticed until flow analysis was actually running again.

Issue #33 — disallow Luau keywords as declaration names

  • New LuauFactory.Keywords set (the 21 standard Lua/Luau keywords + Luau's continue).
  • Resolver.DeclareSymbol now rejects declaring a variable, function, parameter, interface, or trait whose name collides — including inside ambient declare ... signatures, since their emitted forms (e.g. an interface's type X = {...} alias) are still real Luau text.
  • Enum member names are exempt: they're always constant-folded to literal values at compile time and never appear in emitted Luau as an identifier (verified via actual codegen output).
  • self is intentionally excluded (not a real collision case).
  • Found and fixed one existing collision in the shipped intrinsics: a function-named parameter used across several Roblox API bindings (e.g. BindToRenderStep) — renamed to callback in the TypeGenerator's rename map and regenerated None.loom/PluginSecurity.loom to match, otherwise every compile would have immediately failed against the compiler's own bundled bindings.
  • Bonus fix in the same area: PropertyAccess.Render always emitted dot-access with no bracket-access fallback for a keyword-colliding property name — fixed per-segment.

Test plan

  • dotnet build — succeeds, no warnings
  • dotnet test — 1798/1798 pass (1783 existing + 15 new, covering the new diagnostic, the ambient/enum exemptions, the two flow-analysis fixes, and the bracket-access rendering)
  • Compiled a real project through Loom.CLI before/after the perf changes — output byte-identical
  • Verified end-to-end: a trivial project now compiles cleanly against the regenerated intrinsics; declaring a keyword-named let/fn/parameter/interface (including inside declare) now errors with the new diagnostic; an enum with a keyword-named member still compiles and constant-folds correctly

Closes #33

🤖 Generated with Claude Code

R-unic and others added 5 commits July 24, 2026 05:28
Analyze() stopped invoking AnalyzeStatements/BindState on the tree
entirely, so _states was never populated and every flow-dependent
diagnostic (unreachable code, use of uninitialized/maybe-uninitialized
variables, assignment to immutable bindings) silently stopped firing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FlowAnalyzer.AnalyzeStatement had no case for EnumDeclaration (unlike
InterfaceDeclaration/EventDeclaration, which both mark their own symbol
as initialized), so referencing an enum after declaring it incorrectly
tripped "use of uninitialized variable". No existing test exercised an
enum declaration through the full pipeline, so this went unnoticed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Declaring a variable, function, parameter, interface, or trait whose
name is a reserved Luau keyword (and/or, break, do, ..., continue) now
raises a diagnostic in Resolver.DeclareSymbol, since that name would be
invalid wherever Luau emits it literally. Ambient (declare ...) names
are checked too, since their emitted forms (e.g. an interface's `type
X = {...}` alias) are still real Luau text. Enum member names are
exempt: they're always constant-folded to literal values at compile
time and never appear in emitted Luau as an identifier (verified via
LuauGenerator/enum codegen). `self` is intentionally not included.

Renamed the one existing "function" parameter collision (a name used
across several Roblox API method signatures, e.g. BindToRenderStep) to
"callback" in the TypeGenerator's RenameMap and regenerated the shipped
None.loom/PluginSecurity.loom intrinsics to match - otherwise every
compile would immediately fail on the compiler's own bundled bindings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PropertyAccess.Render always emitted dot-access (instance.name), with
no fallback for a name that's a reserved Luau keyword, which would be
invalid Luau (instance.end vs. the required instance["end"]). Each
segment in the access chain is now checked against LuauFactory.Keywords
and rendered as bracket/string-indexed access when it collides, leaving
non-colliding segments untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

Qodana for .NET

245 new problems were found

Inspection name Severity Problems
Auto-property accessor is never used (private accessibility) 🔶 Warning 6
Auto-property accessor is never used (non-private accessibility) 🔶 Warning 5
Non-accessed positional property (non-private accessibility) 🔶 Warning 2
Redundant argument with default value 🔶 Warning 2
Type member is never used (private accessibility) 🔶 Warning 1
Unused parameter (private accessibility) 🔶 Warning 1
Type member is never used (non-private accessibility) ◽️ Notice 94
Use preferred style of 'new' expression when created type is not evident ◽️ Notice 83
Member can be made private (non-private accessibility) ◽️ Notice 18
Class is never instantiated (non-private accessibility) ◽️ Notice 4
Some values of the enum are not processed inside 'switch' statement ◽️ Notice 4
Auto-property can be made get-only (non-private accessibility) ◽️ Notice 3
Invert 'if' statement to reduce nesting ◽️ Notice 3
RoslynAnalyzers The member referenced by the MemberData attribute returns untyped data rows ◽️ Notice 3
Use preferred style for trailing comma before new line in multiline lists ◽️ Notice 2
RoslynAnalyzers Do not use Enumerable methods on indexable collections ◽️ Notice 2
RoslynAnalyzers Use concrete types when possible for improved performance ◽️ Notice 2
Auto-property can be made get-only (private accessibility) ◽️ Notice 1
Foreach loop can be converted into LINQ-expression but another 'GetEnumerator' method will be used ◽️ Notice 1
Merge null/pattern checks into complex pattern ◽️ Notice 1
Redundant string interpolation ◽️ Notice 1
Replace with 'field' keyword ◽️ Notice 1
Some values of the enum are not processed inside 'switch' statement and are handled via default section ◽️ Notice 1
Tail recursive call can be replaced with loop ◽️ Notice 1
Type member is never accessed via base type (non-private accessibility) ◽️ Notice 1
Method return value is never used (non-private accessibility) ◽️ Notice 1
Literal length can be reduced by using verbatim string ◽️ Notice 1

☁️ View the detailed Qodana report

Detected 1 dependency

Third-party software list

This page lists the third-party software dependencies used in Loom

Dependency Version Licenses
Tomlyn 2.6.0 BSD-2-Clause
Contact Qodana team

Contact us at qodana-support@jetbrains.com

@R-unic
R-unic merged commit 6497843 into master Jul 24, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Loom Release 1.0.0 Jul 24, 2026
@R-unic
R-unic deleted the perf/descendants-and-luau-keywords branch July 24, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Disallow Luau keywords

1 participant