Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions tsc/internal/checker/pseudotypenodebuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package checker

import (
"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/debug"
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
"github.com/microsoft/TypeScript/tsc/internal/printer"
Expand Down Expand Up @@ -421,16 +422,29 @@ func (b *NodeBuilderImpl) pseudoTypeEquivalentToType(t *pseudochecker.PseudoType
return false
}
targetProps := b.ch.getPropertiesOfType(undefinedStripped)
// Count total declarations across all target prop symbols to handle getter/setter pairs,
// which are two elements in pt.Elements but only one symbol in targetProps.
targetDeclCount := 0
// Index signature components also represent computed object members. Count each
// declaration once, since a numeric key can contribute to multiple indexes.
targetDeclarations := make(map[*ast.Node]bool)
for _, prop := range targetProps {
targetDeclCount += len(prop.Declarations())
for _, declaration := range prop.Declarations() {
targetDeclarations[declaration] = true
}
}
for _, info := range b.ch.getIndexInfosOfType(undefinedStripped) {
for _, declaration := range info.components {
targetDeclarations[declaration] = true
}
}
if len(pt.Elements) != targetDeclCount {
if len(pt.Elements) != len(targetDeclarations) {
return false
}
hasComputedNames := core.Some(pt.Elements, func(e *pseudochecker.PseudoObjectElement) bool {
return ast.IsComputedPropertyName(e.Name) && ast.IsEntityNameExpression(e.Name.Expression())
})
for _, e := range pt.Elements {
if ast.IsComputedPropertyName(e.Name) && ast.IsEntityNameExpression(e.Name.Expression()) && !b.isTriviallySerializableComputedName(e.Name.Parent) {
return false
}
var targetProp *ast.Symbol
elemSymbol := e.Name.Parent.Symbol()
if elemSymbol != nil {
Expand All @@ -440,11 +454,14 @@ func (b *NodeBuilderImpl) pseudoTypeEquivalentToType(t *pseudochecker.PseudoType
// Name lookup failed or returned no result; search target properties
// for one whose declaration name node matches the one we have
for _, prop := range targetProps {
if prop.ValueDeclaration() != nil && prop.ValueDeclaration().Name() == e.Name {
if core.Some(prop.Declarations(), func(d *ast.Node) bool { return d.Name() == e.Name }) {
targetProp = prop
break
}
}
if targetProp == nil && targetDeclarations[e.Name.Parent] && undefinedStripped.objectFlags&ObjectFlagsInstantiated == 0 {
targetProp = b.ch.getSymbolOfDeclaration(e.Name.Parent)
}
if targetProp == nil {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
Expand All @@ -464,7 +481,7 @@ func (b *NodeBuilderImpl) pseudoTypeEquivalentToType(t *pseudochecker.PseudoType
switch e.Kind {
case pseudochecker.PseudoObjectElementKindPropertyAssignment:
d := e.AsPseudoPropertyAssignment()
if !b.pseudoTypeEquivalentToType(d.Type, propType, e.Optional, false) {
if hasComputedNames && d.Type.Kind == pseudochecker.PseudoTypeKindInferred && len(d.Type.AsPseudoTypeInferred().ErrorNodes) > 0 || !b.pseudoTypeEquivalentToType(d.Type, propType, e.Optional, false) {
if reportErrors {
if d.Type.Kind == pseudochecker.PseudoTypeKindInferred && len(d.Type.AsPseudoTypeInferred().ErrorNodes) > 0 {
// Re-report the fine-grained error nodes; the recursive call used reportErrors=false
Expand Down
12 changes: 10 additions & 2 deletions tsc/internal/pseudochecker/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,14 @@ func (ch *PseudoChecker) typeFromObjectLiteral(node *ast.ObjectLiteralExpression

// roughly analogous to typeFromObjectLiteralAccessor in strada
func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) *PseudoObjectElement {
if ast.IsComputedPropertyName(name) && ast.IsEntityNameExpression(name.Expression()) {
// Computed accessors cannot be paired using their bound symbols alone. Preserve each
// accessor so the declaration checker can resolve the computed names and pair them.
if ast.IsGetAccessorDeclaration(accessor) {
return NewPseudoGetAccessor(accessor, name, false, ch.typeFromAccessor(accessor))
}
return NewPseudoSetAccessor(accessor, name, false, ch.cloneParameters(accessor.ParameterList())[0])
}
allAccessors := ast.GetAllAccessorDeclarationsForDeclaration(accessor, accessor.Symbol().Declarations()) // TODO: node preservation for late-bound accessor pairs?

// TODO: handle pseudo-annotations from get accessor return positions?
Expand Down Expand Up @@ -402,7 +410,7 @@ func (ch *PseudoChecker) getAccessorMember(accessor *ast.Node, name *ast.Node) *

// canGetTypeFromObjectLiteral checks whether an object literal can be typed by the pseudochecker.
// Returns nil if the object can be typed, or a slice of error nodes (shorthand/spread properties,
// non-literal computed names) that prevent typing.
// computed names that cannot be preserved) that prevent typing.
func (ch *PseudoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpression) []*ast.Node {
if node.Properties == nil || len(node.Properties.Nodes) == 0 {
return nil // empty object, ok
Expand All @@ -427,7 +435,7 @@ func (ch *PseudoChecker) canGetTypeFromObjectLiteral(node *ast.ObjectLiteralExpr
}
if e.Name().Kind == ast.KindComputedPropertyName {
expression := e.Name().Expression()
if !ast.IsPrimitiveLiteralValue(expression, false) {
if !ast.IsPrimitiveLiteralValue(expression, false) && !ast.IsEntityNameExpression(expression) {
errorNodes = append(errorNodes, e.Name())
}
}
Expand Down
18 changes: 12 additions & 6 deletions tsc/internal/transformers/declarations/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,19 +574,19 @@ func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast.
}
if ast.HasDynamicName(input) {
if tx.state.isolatedDeclarations {
// Classes and object literals usually elide properties with computed names that are not of a literal type
// In isolated declarations TSC needs to error on these as we don't know the type in a DTE.
if !tx.resolver.IsDefinitelyReferenceToGlobalSymbolObject(input.Name().Expression()) {
// Entity name expressions can be preserved without knowing the type of the computed name.
if !ast.IsEntityNameExpression(input.Name().Expression()) && !tx.resolver.IsDefinitelyReferenceToGlobalSymbolObject(input.Name().Expression()) {
if ast.IsClassDeclaration(input.Parent) || ast.IsObjectLiteralExpression(input.Parent) {
tx.state.addDiagnostic(createDiagnosticForNode(input, diagnostics.Computed_property_names_on_class_or_object_literals_cannot_be_inferred_with_isolatedDeclarations))
return nil
} else if (ast.IsInterfaceDeclaration(input.Parent) || ast.IsTypeLiteralNode(input.Parent)) && !ast.IsEntityNameExpression(input.Name().Expression()) {
} else if ast.IsInterfaceDeclaration(input.Parent) || ast.IsTypeLiteralNode(input.Parent) {
// Type declarations just need to double-check that the input computed name is an entity name expression
tx.state.addDiagnostic(createDiagnosticForNode(input, diagnostics.Computed_properties_must_be_number_or_string_literals_variables_or_dotted_expressions_with_isolatedDeclarations))
return nil
}
}
} else if !tx.resolver.IsLateBound(tx.EmitContext().ParseNode(input)) || !ast.IsEntityNameExpression(input.Name().Expression()) {
}
if !ast.IsEntityNameExpression(input.Name().Expression()) {
return nil
}
}
Expand Down Expand Up @@ -1977,7 +1977,13 @@ func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMe
if privateIdentifier != nil {
memberNodes = append(memberNodes, privateIdentifier)
}
memberNodes = append(memberNodes, lateIndexes...)
// Entity computed names are emitted from the original class members below.
// Keep synthesized indexes only when they do not duplicate those members.
for _, index := range lateIndexes {
if index.Name() == nil || !ast.IsComputedPropertyName(index.Name()) || !ast.IsEntityNameExpression(index.Name().Expression()) {
memberNodes = append(memberNodes, index)
}
}
memberNodes = append(memberNodes, parameterProperties...)
memberNodes = append(memberNodes, extraMembers...)
visitResult := tx.Visitor().VisitNodes(classNode.ClassLikeData().Members)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function foo8(y = (async () => z)(), z = 1) {

// error - used as computed name of method
function foo9(y = {[z]() { return z; }}, z = 1) {
>foo9 : (y?: { [z]: () => number; }, z?: number) => void
>foo9 : (y?: { [z](): number; }, z?: number) => void
>y : { [z]: () => number; }
>{[z]() { return z; }} : { [z]: () => number; }
>[z] : () => number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Mixer = Mix(class {
//// [classNonUniqueSymbolMethodHasSymbolIndexer.d.ts]
declare const a: symbol;
export declare class A {
[a]: () => number;
[a](): number;
}
export declare const Mixer: {
new (): {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
computedPropertiesNarrowed.ts(5,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(11,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(18,20): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(20,5): error TS9010: Variable must have an explicit type annotation with --isolatedDeclarations.
computedPropertiesNarrowed.ts(22,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(26,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(31,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(37,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(42,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
computedPropertiesNarrowed.ts(47,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.


==== computedPropertiesNarrowed.ts (10 errors) ====
==== computedPropertiesNarrowed.ts (5 errors) ====
const x: 0 | 1 = Math.random()? 0: 1;
declare function assert(n: number): asserts n is 1;
assert(x);
export let o = {
[x]: 1 // error narrow type !== declared type
~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 computedPropertiesNarrowed.ts:4:12: Add a type annotation to the variable o.
}


const y: 0 = 0
export let o2 = {
[y]: 1 // ok literal computed type
~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 computedPropertiesNarrowed.ts:10:12: Add a type annotation to the variable o2.
}

// literals are ok
Expand All @@ -45,9 +34,6 @@ computedPropertiesNarrowed.ts(47,5): error TS9038: Computed property names on cl
!!! related TS9027 computedPropertiesNarrowed.ts:20:5: Add a type annotation to the variable u.
export let o4 = {
[u]: 1 // Should error, nut a unique symbol
~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 computedPropertiesNarrowed.ts:21:12: Add a type annotation to the variable o4.
}

export let o5 ={
Expand All @@ -60,9 +46,6 @@ computedPropertiesNarrowed.ts(47,5): error TS9038: Computed property names on cl
const uu: unique symbol = Symbol();
export let o6 = {
[uu]: 1 // Should be ok
~~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 computedPropertiesNarrowed.ts:30:12: Add a type annotation to the variable o6.
}


Expand All @@ -77,9 +60,6 @@ computedPropertiesNarrowed.ts(47,5): error TS9038: Computed property names on cl
let E = { A: 1 } as const
export const o8 = {
[E.A]: 1 // Fresh
~~~~~
!!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations.
!!! related TS9027 computedPropertiesNarrowed.ts:41:14: Add a type annotation to the variable o8.
}

function ns() { return { v: 0 } as const }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ exports.C = C;
//// [main.d.ts]
import Test from "abcdefgh";
export declare class C {
[Test.someKey]: () => void;
[Test.someKey](): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ exports.a = (new WithData())["ahahahaahah"]();
//// [declarationEmitComputedNameWithQuestionToken.d.ts]
export declare const dataSomething: `data-${string}`;
export declare class WithData {
[dataSomething]?: (() => string) | undefined;
[dataSomething]?(): string;
}
export declare const a: string;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export const foo = { ...({} as Type) };

//// [type.d.ts]
export type Type = {
x?: {};
x?: {
[Enum.A]: 0;
};
};
//// [index.d.ts]
export declare const foo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export declare namespace Foo {
}
}
export type Type = {
x?: {};
x?: {
[Foo.Enum]: 0;
};
};
//// [index.d.ts]
export declare const foo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ declare const STATUS: {
};
declare const hundredStr = "100";
declare const obj: {
"100": string;
[hundredStr]: string;
};
declare const hundredNum = 100;
declare const obj2: {
100: string;
[hundredNum]: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@ exports.instanceLookup = (new Holder())["some" + "thing"];
//// [declarationEmitSimpleComputedNames1.d.ts]
export declare const fieldName: string;
export declare const conatainer: {
[fieldName]: () => string;
[fieldName](): string;
};
declare const classFieldName: string;
declare const otherField: string;
declare const staticField: string;
export declare class Holder {
static [staticField]: () => {
[classFieldName](): string;
[otherField](): number;
static [staticField](): {
static: boolean;
};
static [staticField]: () => {
static [staticField](): {
static: string;
};
[classFieldName]: () => string;
[otherField]: () => number;
}
/**
* Could be `"prototype"`, so all static string indexers include the instance type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Experiment<Name> = {
declare const _default: Experiment<"foo">;
export default _default;
//// [main.d.ts]
import other from "./other";
export declare const obj: {
foo: number;
[other.name]: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Experiment<Name> = {
declare const _default: Experiment<"foo">;
export default _default;
//// [main.d.ts]
import * as other2 from "./other";
export declare const obj: {
foo: number;
[other2.default.name]: number;
};
6 changes: 3 additions & 3 deletions tsc/testdata/baselines/reference/compiler/dynamicNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ export declare const c4 = "a";
export declare const c5 = 1;
export declare const s2: typeof s0;
export declare const o1: {
a: number;
1: string;
[s0]: boolean;
[c4]: number;
[c5]: string;
[s2]: boolean;
};
export declare const o1_c4: number;
export declare const o1_c5: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export type ObjectTypeVisibility = {
export declare const ObjectLiteralVisibility: {
[x]: number;
[y](): number;
readonly [z]: number;
[w]: number;
get [z](): number;
set [w](value: number);
};
export {};
Loading