Skip to content

Provide implicit index signatures for JS expando object literals#3808

Open
ahejlsberg wants to merge 4 commits into
mainfrom
fix-3739
Open

Provide implicit index signatures for JS expando object literals#3808
ahejlsberg wants to merge 4 commits into
mainfrom
fix-3739

Conversation

@ahejlsberg
Copy link
Copy Markdown
Member

With this PR we consistently provide implicit index signatures for JS expando object literals. We're currently in a very confused state in this area as discussed here.

Note that this will resurrect the error observed in #3691, but that was fixed in the belief that TS6 was consistent here, which it isn't. In particular, the repro from #3691 errors when contained in a function or method, but succeeds at top-level. With this PR we'll consistently catch the circularity and report it with noImplicitAny: true.

Fixes #3739.

Copy link
Copy Markdown
Contributor

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

This PR adjusts how the checker treats JavaScript “expando” object literals with respect to inferred/implicit index signatures, aiming to make behavior consistent across scopes and to align diagnostics with the expected TypeScript behavior described in #3739/#3691.

Changes:

  • Updates Checker.isObjectTypeWithInferableIndex to treat more object types (including JS literals) as having an inferable index.
  • Adds a new compiler test (expandoObjectIndexSignatures) with corresponding reference baselines.
  • Updates expandoNoInferredIndex baselines to reflect the (re-)introduced TS7022 circularity/implicit-any diagnostic.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/checker/relater.go Adjusts the predicate that decides when an object can be treated as having an inferable index signature.
testdata/tests/cases/compiler/expandoObjectIndexSignatures.ts Adds coverage intended to exercise expando object literal index-signature behavior.
testdata/baselines/reference/compiler/expandoObjectIndexSignatures.types Reference types baseline for the new test.
testdata/baselines/reference/compiler/expandoObjectIndexSignatures.symbols Reference symbols baseline for the new test.
testdata/baselines/reference/compiler/expandoObjectIndexSignatures.errors.txt Reference error baseline for the new test.
testdata/baselines/reference/compiler/expandoNoInferredIndex.types Updates expected types output due to the behavior change.
testdata/baselines/reference/compiler/expandoNoInferredIndex.errors.txt Adds/updates expected diagnostic output for TS7022 in the existing scenario.

Comment thread internal/checker/relater.go Outdated
Comment on lines +4586 to +4595
t.objectFlags&ObjectFlagsObjectRestType != 0 ||
t.symbol.Flags&(ast.SymbolFlagsObjectLiteral|ast.SymbolFlagsTypeLiteral|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 &&
t.symbol.Flags&ast.SymbolFlagsClass == 0 && !c.typeHasCallOrConstructSignatures(t) ||
t.objectFlags&(ObjectFlagsJSLiteral|ObjectFlagsObjectRestType) != 0 ||
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will update comment.

Comment on lines 4592 to 4596
return t.symbol != nil &&
(t.symbol.Flags&(ast.SymbolFlagsObjectLiteral|ast.SymbolFlagsTypeLiteral) != 0 && len(t.symbol.Exports) == 0 ||
t.symbol.Flags&(ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 && t.symbol.Flags&ast.SymbolFlagsClass == 0) &&
!c.typeHasCallOrConstructSignatures(t) ||
t.objectFlags&ObjectFlagsObjectRestType != 0 ||
t.symbol.Flags&(ast.SymbolFlagsObjectLiteral|ast.SymbolFlagsTypeLiteral|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 &&
t.symbol.Flags&ast.SymbolFlagsClass == 0 && !c.typeHasCallOrConstructSignatures(t) ||
t.objectFlags&(ObjectFlagsJSLiteral|ObjectFlagsObjectRestType) != 0 ||
t.objectFlags&ObjectFlagsReverseMapped != 0 && c.isObjectTypeWithInferableIndex(t.AsReverseMappedType().source)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, it's fine the way it is.

Comment on lines +8 to +16
/** @param {Record<string, number>} obj */
function fx(obj) {}

const obj1 = {};
fx(obj1);

const obj2 = {};
obj2.x = 1
fx(obj2);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The desired behavior is for an empty {} to be assignable to Record<string, ...>, so tsgo was already doing it right. But that wasn't the case for an expando object literal. We don't need more test cases.

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.

Behavior difference: Variable with type {} can be "wrongly" assigned to Record in tsgo but not in tsc

2 participants