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
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 11 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ import {
InterfaceDeclaration,
InternalEmitFlags,
InternalSymbolName,
IntersectionType,
IntroducesNewScopeNode,
isAccessor,
isAnyDirectorySeparator,
Expand Down Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
literalIntersectionPropertyName.ts(24,7): error TS2322: Type '{ Foo: {}; }' is not assignable to type 'Provides<TypeID<Foo, "Foo">>'.
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<Type = unknown, ID extends string = string> = ID & { [typeKey]?: Type };

function typeID<Type, ID extends string>(id: ID): TypeID<Type, ID> {
return id;
}

type KeyOf<TID extends TypeID> = TID extends TypeID<any, infer ID> ? ID : never;

type TypeOf<TID extends TypeID> = TID extends TypeID<infer Type> ? Type : never;

type Provides<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };

// ---cut---

interface Foo {
foo(): void;
}

const Foo = typeID("Foo") satisfies TypeID<Foo>;
// ^? const Foo: TypeID<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
~~~
!!! error TS2322: Type '{ Foo: {}; }' is not assignable to type 'Provides<TypeID<Foo, "Foo">>'.
!!! 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]: {}
};
40 changes: 40 additions & 0 deletions tests/baselines/reference/literalIntersectionPropertyName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [tests/cases/compiler/literalIntersectionPropertyName.ts] ////

//// [literalIntersectionPropertyName.ts]
declare const typeKey: unique symbol;

type TypeID<Type = unknown, ID extends string = string> = ID & { [typeKey]?: Type };

function typeID<Type, ID extends string>(id: ID): TypeID<Type, ID> {
return id;
}

type KeyOf<TID extends TypeID> = TID extends TypeID<any, infer ID> ? ID : never;

type TypeOf<TID extends TypeID> = TID extends TypeID<infer Type> ? Type : never;

type Provides<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };

// ---cut---

interface Foo {
foo(): void;
}

const Foo = typeID("Foo") satisfies TypeID<Foo>;
// ^? const Foo: TypeID<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
[Foo]: {}
};

//// [literalIntersectionPropertyName.js]
var _a;
function typeID(id) {
return id;
}
var Foo = typeID("Foo");
// ^? const Foo: TypeID<Foo, "Foo">
var Bar = (_a = {},
_a[Foo] = {},
_a);
84 changes: 84 additions & 0 deletions tests/baselines/reference/literalIntersectionPropertyName.symbols
Original file line number Diff line number Diff line change
@@ -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<Type = unknown, ID extends string = string> = 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<Type, ID extends string>(id: ID): TypeID<Type, ID> {
>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> = TID extends TypeID<any, infer ID> ? 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> = TID extends TypeID<infer Type> ? 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<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };
>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>;
>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<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
>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))

};
77 changes: 77 additions & 0 deletions tests/baselines/reference/literalIntersectionPropertyName.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//// [tests/cases/compiler/literalIntersectionPropertyName.ts] ////

=== literalIntersectionPropertyName.ts ===
declare const typeKey: unique symbol;
>typeKey : unique symbol
> : ^^^^^^^^^^^^^

type TypeID<Type = unknown, ID extends string = string> = ID & { [typeKey]?: Type };
>TypeID : TypeID<Type, ID>
> : ^^^^^^^^^^^^^^^^
>[typeKey] : Type
> : ^^^^
>typeKey : unique symbol
> : ^^^^^^^^^^^^^

function typeID<Type, ID extends string>(id: ID): TypeID<Type, ID> {
>typeID : <Type, ID extends string>(id: ID) => TypeID<Type, ID>
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^^^^
>id : ID
> : ^^

return id;
>id : ID
> : ^^
}

type KeyOf<TID extends TypeID> = TID extends TypeID<any, infer ID> ? ID : never;
>KeyOf : KeyOf<TID>
> : ^^^^^^^^^^

type TypeOf<TID extends TypeID> = TID extends TypeID<infer Type> ? Type : never;
>TypeOf : TypeOf<TID>
> : ^^^^^^^^^^^

type Provides<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };
>Provides : Provides<P>
> : ^^^^^^^^^^^

// ---cut---

interface Foo {
foo(): void;
>foo : () => void
> : ^^^^^^
}

const Foo = typeID("Foo") satisfies TypeID<Foo>;
>Foo : TypeID<Foo, "Foo">
> : ^^^^^^^^^^^^^^^^^^
>typeID("Foo") satisfies TypeID<Foo> : TypeID<Foo, "Foo">
> : ^^^^^^^^^^^^^^^^^^
>typeID("Foo") : TypeID<Foo, "Foo">
> : ^^^^^^^^^^^^^^^^^^
>typeID : <Type, ID extends string>(id: ID) => TypeID<Type, ID>
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^^^^
>"Foo" : "Foo"
> : ^^^^^

// ^? const Foo: TypeID<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
>Bar : Provides<TypeID<Foo, "Foo">>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Foo : TypeID<Foo, "Foo">
> : ^^^^^^^^^^^^^^^^^^
>{ [Foo]: {}} : { Foo: {}; }
> : ^^^^^^^^^^^^

[Foo]: {}
>[Foo] : {}
> : ^^
>Foo : TypeID<Foo, "Foo">
> : ^^^^^^^^^^^^^^^^^^
>{} : {}
> : ^^

};
26 changes: 26 additions & 0 deletions tests/cases/compiler/literalIntersectionPropertyName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
declare const typeKey: unique symbol;

type TypeID<Type = unknown, ID extends string = string> = ID & { [typeKey]?: Type };

function typeID<Type, ID extends string>(id: ID): TypeID<Type, ID> {
return id;
}

type KeyOf<TID extends TypeID> = TID extends TypeID<any, infer ID> ? ID : never;

type TypeOf<TID extends TypeID> = TID extends TypeID<infer Type> ? Type : never;

type Provides<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };

// ---cut---

interface Foo {
foo(): void;
}

const Foo = typeID("Foo") satisfies TypeID<Foo>;
// ^? const Foo: TypeID<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
[Foo]: {}
};
Loading