Skip to content

Add {}/class-expression expandos #885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented May 20, 2025

This PR adds support for expando properties on {} and class expressions in Javascript. It also adds support for expandos on expandos in Javascript:

var o = {}
o.C = class { // expando on o
  constructor() {
    this.o = {}
    this.o.p = 12 // expando on this.o
  }
}
o.C.x = 13 // expando on o.C

It also works with element accesses as well as property accesses.

Implementation notes

  • The PR, unfortunately, brings back some of the complexity that the binder uses to detect these different patterns. But it's still nothing like the checks in Strada.
  • I discovered that Strada's thisParentContainer tracking was not complete, so it relies on getThisContainer, which isn't safe to use in the binder because not all parent pointers are set. I added a specific ContainerFlags for this, and the resulting code should be simpler and safer.
  • Checking of {} expandos is different because the exports are on the object literal, not the declaring variable. The binding is now consistent between {}, class, function expressions, and the checker code to support the old way would be at least as bad.
  • The code to avoid type checking circularity for expando-{} means that those expando properties don't widen. I'll return to this later.
  • While adding the code to contextually type expando properties when there's a type annotation for the container, I made the code more like the Strada original.

The binder diff is a lot easier to read with whitespace ignored since I pulled out some code into a separate function.

Also fix up this-container tracking and assignment-declaration
contextual typing.
@Copilot Copilot AI review requested due to automatic review settings May 20, 2025 17:32
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds support for “expando” properties on empty object literals and class expressions in JavaScript, including nested expandos and element-access syntax. Key changes include binder enhancements to recognize and bind expando assignments, checker updates to handle expando types without circularity, and updated test baselines to reflect new type, symbol, and error outputs.

  • Introduce bindExpandoPropertyAssignment with getInitializerSymbol and isExpandoInitializer in the binder
  • Extend checker to skip the usual object-literal paths for expandos and adjust contextual typing
  • Update utilities and AST to recognize JS entity names and improve error context
  • Refresh numerous testdata baselines for types, symbols, and errors

Reviewed Changes

Copilot reviewed 240 out of 240 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/checker/checker.go Skip original literal-check paths for expando object literals and adjust widening/contextual typing logic
internal/binder/binder.go Add ContainerFlagsIsThisContainer, bindExpandoPropertyAssignment, getInitializerSymbol, and helper logic for expandos
internal/ast/utilities.go Extend GetAssignmentDeclarationKind and entity-name checks to include JS and element access patterns
internal/ast/ast.go Improve panic messages in Node.Expression and Node.Arguments with kind info
testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.types Updated expected types for enum and expando export support
testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.symbols.diff Adjusted symbol diff to include new expando symbols
testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.symbols Updated baseline symbols for cross-file enum exports
testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt Refined expected errors after enabling expandos
testdata/baselines/reference/submodule/compiler/jsElementAccessNoContextualTypeCrash.types Added expected types for Common.localize expando via element access
testdata/baselines/reference/submodule/compiler/jsElementAccessNoContextualTypeCrash.symbols.diff Updated symbol diff for element-accessed expandos
testdata/baselines/reference/submodule/compiler/jsElementAccessNoContextualTypeCrash.symbols Refreshed symbols baseline for Common.localize
testdata/baselines/reference/submodule/compiler/jsElementAccessNoContextualTypeCrash.errors.txt Adjusted errors for the Common assignment when widening types
testdata/baselines/reference/submodule/compiler/ensureNoCrashExportAssignmentDefineProperrtyPotentialMerge.types Updated types for module.exports and Object.defineProperty expandos
testdata/baselines/reference/submodule/compiler/ensureNoCrashExportAssignmentDefineProperrtyPotentialMerge.symbols.diff Added symbol diffs for merged export assignments
testdata/baselines/reference/submodule/compiler/ensureNoCrashExportAssignmentDefineProperrtyPotentialMerge.symbols Refreshed symbols baseline after merging defineProperty and export assignments
testdata/baselines/reference/submodule/compiler/ensureNoCrashExportAssignmentDefineProperrtyPotentialMerge.errors.txt Cleaned up expected error counts and messages
testdata/baselines/reference/submodule/compiler/classFieldSuperAccessibleJs1.types Updated expected types for super-accessible static class fields
testdata/baselines/reference/submodule/compiler/classFieldSuperAccessibleJs1.symbols.diff Adjusted symbol diffs for super.blah2
testdata/baselines/reference/submodule/compiler/classFieldSuperAccessibleJs1.symbols Refreshed symbols baseline for class-field super access
testdata/baselines/reference/submodule/compiler/classFieldSuperAccessibleJs1.errors.txt Removed obsolete errors now resolved by expando support
Comments suppressed due to low confidence (2)

internal/binder/binder.go:39

  • [nitpick] The flag name ContainerFlagsIsThisContainer is slightly verbose and inconsistent with other flags (drop Is or reorder words). Consider ContainerFlagsThisContainer for consistency.
ContainerFlagsIsThisContainer                                  ContainerFlags = 1 << 8

internal/ast/utilities.go:1478

  • [nitpick] The parameter allowJS may be unclear in intent; consider renaming to something like allowInJSContext or includeJS to clarify its effect.
func IsEntityNameExpressionEx(node *Node, allowJS bool) bool {

@@ -16926,6 +16931,11 @@ func (c *Checker) getWidenedTypeWithContext(t *Type, context *WideningContext) *
case t.flags&(TypeFlagsAny|TypeFlagsNullable) != 0:
result = c.anyType
case isObjectLiteralType(t):
// expando object literal types do not widen to prevent circular checking (but should, ideally)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a TODO that I'll get back to later

if localsContainer != nil {
local := localsContainer.Locals[name]
if local != nil {
if localsContainer := container.LocalsContainerData(); localsContainer != nil {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a style improvement--at least I hope it's an improvement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant