feat: allow attributes on event declarations - #52
Merged
Conversation
The LuauGenerator's event-assignment gate only recognized bare identifier events tracked via a Resolver Symbol, so `eo.consumer += handler` (an interface member event accessed through a variable) fell through to generic binary-operator codegen and emitted invalid Luau (`eo.consumer += ...`) instead of a `:Connect()` call. The TypeChecker already resolved these structurally via the interface's ObjectType, so this was purely a codegen gap. - SemanticModel: extract the structural expression -> PropertySymbol walk out of TryGetIntrinsicAttribute into a new public GetPropertySymbol, so codegen can resolve "what interface member does this expression refer to" without needing a resolver Symbol. - EventConnectionTracker: widen the tracked-connection key from a bare Symbol to an EventTarget (event + owning instance), so two variables of the same interface type don't collide on the same tracked connection. - LuauGenerator: resolve the event target for both global events (Symbol-based, unchanged) and interface member events (structural, via GetPropertySymbol) before generating Connect/Disconnect calls. Also emit a `Loom.Event<...>` field for `event` members in generated interface types, which were previously dropped entirely. - TypeChecker: extend the event-invocation arity/optional-argument check to also look up property-access-invoked events structurally. Adds interface-member-event coverage to LuauGeneratorTest and TypeCheckerTest, and a new interface_events Luau snapshot pair.
Adds attribute support to EventDeclaration (both interface-member and global/top-level forms, which share one AST node), so `luau_name` and other attributes parse and resolve/type-check cleanly in both contexts. - EventDeclaration implements IWithAttributes and gains TryGetIntrinsicAttribute, mirroring PropertyDeclaration. Because interface-member events end up with two symbols registered against the same declaration node (a plain Symbol from the Resolver's generic traversal, and the real PropertySymbol with attributes from ResolveInterfaceBody), the lookup searches all symbols for the node rather than assuming a single match, so it correctly finds the PropertySymbol for interface events and correctly finds nothing for global events. - Parser: interface bodies now check for `event` before falling back to property parsing after a bracketed attribute list, and a new bracket-depth lookahead lets top-level `[attr] event ...;` parse without disturbing plain array-literal statements. - Resolver builds real AttributeSymbols for interface events, the same way it already does for properties. - TypeChecker visits event attribute expressions in both the global event path and interface event resolution, so `luau_name` must resolve to something function-typed, same as for properties. - LuauGenerator's interface event codegen honors `luau_name` to rename the table field, matching property behavior; existing renamed-access codegen already worked generically once the Resolver populated attributes. Global event codegen is untouched by design: attributes have no effect on generated code for global events, since they compile to an internal Loom.Event.new() local with no external Luau identity to rename. Tests: parser (both contexts, plus a regression guard that plain array literals aren't misparsed as attribute lists), resolver (attribute population for interface events, no-op for global events), type checker (clean type-check plus NonFunctionAttribute diagnostic in both contexts), and a new Luau snapshot pair plus targeted codegen assertions locking in the field/access-site rename for interface events and the "no effect" behavior for global events. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Qodana for .NET253 new problems were found
☁️ View the detailed Qodana report Detected 1 dependencyThird-party software listThis page lists the third-party software dependencies used in Loom
Contact Qodana teamContact us at qodana-support@jetbrains.com
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
eventdeclarations can now carry attributes (e.g.[luau_name("OnConsume")] event consumer(x: string);) in both interface-member and top-level/global positions — previously onlyPropertyDeclarationimplementedIWithAttributes, and the parser had no path to attach a bracketed attribute list to aneventin either context.luau_namerenaming behavior properties already have: the generated Luau table field and every.nameaccess site are renamed. This is intentional and mirrors the existing convention for properties, since interface members often model pre-existing Roblox API shapes.Loom.Event.new()local with no external Luau identity worth renaming.LooksLikeAttributesBeforeEventparser lookahead so top-level[attr] event ...;parses without breaking ordinary array-literal statements ([1, 2, 3];).Stacked on #51 (
fix/interface-member-event-codegen) — this PR's diff will shrink to just the attribute-support changes once that one merges.Test plan
dotnet build/dotnet build -c Releasedotnet test/dotnet test -c Release— full suite passes (1773/1773)ParserTestcoverage: attributes parse on events in both contexts, plain array-literal statements are unaffectedResolverTest/TypeCheckerTestcoverage: attribute symbols populate for interface events, global event attributes resolve with no diagnostics and stay a plainSymbolLoom.Testing/Snapshots/Luau/event_luau_name_attribute.loom/.luau: interface event renames table field + access sites; global event with the same attribute emits unchanged