diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fcb93c45dc1dd..e41d50608fa61 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28984,7 +28984,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { !!getApplicableIndexInfoForName(type, propName) || !assumeTrue; } - function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) { + function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType | IntersectionType, assumeTrue: boolean) { const name = getPropertyNameFromType(nameType); const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true)); if (isKnownProperty) { @@ -33034,7 +33034,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkNodeDeferred(memberDecl); } - if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { + if (computedNameType && !isTypeUsableAsPropertyName(computedNameType)) { if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { if (isTypeAssignableTo(computedNameType, numberType)) { hasComputedNumberProperty = true; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5755369134d8a..056e2de22384b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -248,6 +248,7 @@ import { InterfaceDeclaration, InternalEmitFlags, InternalSymbolName, + IntersectionType, IntroducesNewScopeNode, isAccessor, isAnyDirectorySeparator, @@ -11057,21 +11058,28 @@ export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName): * Indicates whether a type can be used as a property name. * @internal */ -export function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { - return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); +export function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType | IntersectionType { + return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique) || + !!(type.flags & TypeFlags.Intersection) && some((type as IntersectionType).types, isTypeUsableAsPropertyName); } /** * Gets the symbolic name for a member from its type. * @internal */ -export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { +export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType | IntersectionType): __String { if (type.flags & TypeFlags.UniqueESSymbol) { return (type as UniqueESSymbolType).escapedName; } if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value); } + if (type.flags & TypeFlags.Intersection) { + const element = find((type as IntersectionType).types, isTypeUsableAsPropertyName); + if (element !== undefined) { + return getPropertyNameFromType(element); + } + } return Debug.fail(); } diff --git a/tests/baselines/reference/literalIntersectionPropertyName.errors.txt b/tests/baselines/reference/literalIntersectionPropertyName.errors.txt new file mode 100644 index 0000000000000..a21c5c580c4b8 --- /dev/null +++ b/tests/baselines/reference/literalIntersectionPropertyName.errors.txt @@ -0,0 +1,37 @@ +literalIntersectionPropertyName.ts(24,7): error TS2322: Type '{ Foo: {}; }' is not assignable to type 'Provides>'. + Types of property 'Foo' are incompatible. + Property 'foo' is missing in type '{}' but required in type 'Foo'. + + +==== literalIntersectionPropertyName.ts (1 errors) ==== + declare const typeKey: unique symbol; + + type TypeID = ID & { [typeKey]?: Type }; + + function typeID(id: ID): TypeID { + return id; + } + + type KeyOf = TID extends TypeID ? ID : never; + + type TypeOf = TID extends TypeID ? Type : never; + + type Provides

= { readonly [T in KeyOf

]: TypeOf

}; + + // ---cut--- + + interface Foo { + foo(): void; + } + + const Foo = typeID("Foo") satisfies TypeID; + // ^? const Foo: TypeID + + const Bar: Provides = { + ~~~ +!!! error TS2322: Type '{ Foo: {}; }' is not assignable to type 'Provides>'. +!!! error TS2322: Types of property 'Foo' are incompatible. +!!! error TS2322: Property 'foo' is missing in type '{}' but required in type 'Foo'. +!!! related TS2728 literalIntersectionPropertyName.ts:18:5: 'foo' is declared here. + [Foo]: {} + }; \ No newline at end of file diff --git a/tests/baselines/reference/literalIntersectionPropertyName.js b/tests/baselines/reference/literalIntersectionPropertyName.js new file mode 100644 index 0000000000000..3f09c2f3f3318 --- /dev/null +++ b/tests/baselines/reference/literalIntersectionPropertyName.js @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/literalIntersectionPropertyName.ts] //// + +//// [literalIntersectionPropertyName.ts] +declare const typeKey: unique symbol; + +type TypeID = ID & { [typeKey]?: Type }; + +function typeID(id: ID): TypeID { + return id; +} + +type KeyOf = TID extends TypeID ? ID : never; + +type TypeOf = TID extends TypeID ? Type : never; + +type Provides

= { readonly [T in KeyOf

]: TypeOf

}; + +// ---cut--- + +interface Foo { + foo(): void; +} + +const Foo = typeID("Foo") satisfies TypeID; +// ^? const Foo: TypeID + +const Bar: Provides = { + [Foo]: {} +}; + +//// [literalIntersectionPropertyName.js] +var _a; +function typeID(id) { + return id; +} +var Foo = typeID("Foo"); +// ^? const Foo: TypeID +var Bar = (_a = {}, + _a[Foo] = {}, + _a); diff --git a/tests/baselines/reference/literalIntersectionPropertyName.symbols b/tests/baselines/reference/literalIntersectionPropertyName.symbols new file mode 100644 index 0000000000000..3400aee4238ba --- /dev/null +++ b/tests/baselines/reference/literalIntersectionPropertyName.symbols @@ -0,0 +1,84 @@ +//// [tests/cases/compiler/literalIntersectionPropertyName.ts] //// + +=== literalIntersectionPropertyName.ts === +declare const typeKey: unique symbol; +>typeKey : Symbol(typeKey, Decl(literalIntersectionPropertyName.ts, 0, 13)) + +type TypeID = ID & { [typeKey]?: Type }; +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 2, 12)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 2, 27)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 2, 27)) +>[typeKey] : Symbol([typeKey], Decl(literalIntersectionPropertyName.ts, 2, 64)) +>typeKey : Symbol(typeKey, Decl(literalIntersectionPropertyName.ts, 0, 13)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 2, 12)) + +function typeID(id: ID): TypeID { +>typeID : Symbol(typeID, Decl(literalIntersectionPropertyName.ts, 2, 84)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 4, 16)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 4, 21)) +>id : Symbol(id, Decl(literalIntersectionPropertyName.ts, 4, 41)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 4, 21)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 4, 16)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 4, 21)) + + return id; +>id : Symbol(id, Decl(literalIntersectionPropertyName.ts, 4, 41)) +} + +type KeyOf = TID extends TypeID ? ID : never; +>KeyOf : Symbol(KeyOf, Decl(literalIntersectionPropertyName.ts, 6, 1)) +>TID : Symbol(TID, Decl(literalIntersectionPropertyName.ts, 8, 11)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>TID : Symbol(TID, Decl(literalIntersectionPropertyName.ts, 8, 11)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 8, 62)) +>ID : Symbol(ID, Decl(literalIntersectionPropertyName.ts, 8, 62)) + +type TypeOf = TID extends TypeID ? Type : never; +>TypeOf : Symbol(TypeOf, Decl(literalIntersectionPropertyName.ts, 8, 80)) +>TID : Symbol(TID, Decl(literalIntersectionPropertyName.ts, 10, 12)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>TID : Symbol(TID, Decl(literalIntersectionPropertyName.ts, 10, 12)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 10, 58)) +>Type : Symbol(Type, Decl(literalIntersectionPropertyName.ts, 10, 58)) + +type Provides

= { readonly [T in KeyOf

]: TypeOf

}; +>Provides : Symbol(Provides, Decl(literalIntersectionPropertyName.ts, 10, 80)) +>P : Symbol(P, Decl(literalIntersectionPropertyName.ts, 12, 14)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>T : Symbol(T, Decl(literalIntersectionPropertyName.ts, 12, 46)) +>KeyOf : Symbol(KeyOf, Decl(literalIntersectionPropertyName.ts, 6, 1)) +>P : Symbol(P, Decl(literalIntersectionPropertyName.ts, 12, 14)) +>TypeOf : Symbol(TypeOf, Decl(literalIntersectionPropertyName.ts, 8, 80)) +>P : Symbol(P, Decl(literalIntersectionPropertyName.ts, 12, 14)) + +// ---cut--- + +interface Foo { +>Foo : Symbol(Foo, Decl(literalIntersectionPropertyName.ts, 12, 74), Decl(literalIntersectionPropertyName.ts, 20, 5)) + + foo(): void; +>foo : Symbol(Foo.foo, Decl(literalIntersectionPropertyName.ts, 16, 15)) +} + +const Foo = typeID("Foo") satisfies TypeID; +>Foo : Symbol(Foo, Decl(literalIntersectionPropertyName.ts, 12, 74), Decl(literalIntersectionPropertyName.ts, 20, 5)) +>typeID : Symbol(typeID, Decl(literalIntersectionPropertyName.ts, 2, 84)) +>TypeID : Symbol(TypeID, Decl(literalIntersectionPropertyName.ts, 0, 37)) +>Foo : Symbol(Foo, Decl(literalIntersectionPropertyName.ts, 12, 74), Decl(literalIntersectionPropertyName.ts, 20, 5)) + +// ^? const Foo: TypeID + +const Bar: Provides = { +>Bar : Symbol(Bar, Decl(literalIntersectionPropertyName.ts, 23, 5)) +>Provides : Symbol(Provides, Decl(literalIntersectionPropertyName.ts, 10, 80)) +>Foo : Symbol(Foo, Decl(literalIntersectionPropertyName.ts, 12, 74), Decl(literalIntersectionPropertyName.ts, 20, 5)) + + [Foo]: {} +>[Foo] : Symbol([Foo], Decl(literalIntersectionPropertyName.ts, 23, 35)) +>Foo : Symbol(Foo, Decl(literalIntersectionPropertyName.ts, 12, 74), Decl(literalIntersectionPropertyName.ts, 20, 5)) + +}; diff --git a/tests/baselines/reference/literalIntersectionPropertyName.types b/tests/baselines/reference/literalIntersectionPropertyName.types new file mode 100644 index 0000000000000..5fbd90f9f0e48 --- /dev/null +++ b/tests/baselines/reference/literalIntersectionPropertyName.types @@ -0,0 +1,77 @@ +//// [tests/cases/compiler/literalIntersectionPropertyName.ts] //// + +=== literalIntersectionPropertyName.ts === +declare const typeKey: unique symbol; +>typeKey : unique symbol +> : ^^^^^^^^^^^^^ + +type TypeID = ID & { [typeKey]?: Type }; +>TypeID : TypeID +> : ^^^^^^^^^^^^^^^^ +>[typeKey] : Type +> : ^^^^ +>typeKey : unique symbol +> : ^^^^^^^^^^^^^ + +function typeID(id: ID): TypeID { +>typeID : (id: ID) => TypeID +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>id : ID +> : ^^ + + return id; +>id : ID +> : ^^ +} + +type KeyOf = TID extends TypeID ? ID : never; +>KeyOf : KeyOf +> : ^^^^^^^^^^ + +type TypeOf = TID extends TypeID ? Type : never; +>TypeOf : TypeOf +> : ^^^^^^^^^^^ + +type Provides

= { readonly [T in KeyOf

]: TypeOf

}; +>Provides : Provides

+> : ^^^^^^^^^^^ + +// ---cut--- + +interface Foo { + foo(): void; +>foo : () => void +> : ^^^^^^ +} + +const Foo = typeID("Foo") satisfies TypeID; +>Foo : TypeID +> : ^^^^^^^^^^^^^^^^^^ +>typeID("Foo") satisfies TypeID : TypeID +> : ^^^^^^^^^^^^^^^^^^ +>typeID("Foo") : TypeID +> : ^^^^^^^^^^^^^^^^^^ +>typeID : (id: ID) => TypeID +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>"Foo" : "Foo" +> : ^^^^^ + +// ^? const Foo: TypeID + +const Bar: Provides = { +>Bar : Provides> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Foo : TypeID +> : ^^^^^^^^^^^^^^^^^^ +>{ [Foo]: {}} : { Foo: {}; } +> : ^^^^^^^^^^^^ + + [Foo]: {} +>[Foo] : {} +> : ^^ +>Foo : TypeID +> : ^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + +}; diff --git a/tests/cases/compiler/literalIntersectionPropertyName.ts b/tests/cases/compiler/literalIntersectionPropertyName.ts new file mode 100644 index 0000000000000..e2a6997038e82 --- /dev/null +++ b/tests/cases/compiler/literalIntersectionPropertyName.ts @@ -0,0 +1,26 @@ +declare const typeKey: unique symbol; + +type TypeID = ID & { [typeKey]?: Type }; + +function typeID(id: ID): TypeID { + return id; +} + +type KeyOf = TID extends TypeID ? ID : never; + +type TypeOf = TID extends TypeID ? Type : never; + +type Provides

= { readonly [T in KeyOf

]: TypeOf

}; + +// ---cut--- + +interface Foo { + foo(): void; +} + +const Foo = typeID("Foo") satisfies TypeID; +// ^? const Foo: TypeID + +const Bar: Provides = { + [Foo]: {} +}; \ No newline at end of file