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
22 changes: 20 additions & 2 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17402,10 +17402,17 @@ func (c *Checker) getConstraintFromTypeParameter(t *Type) *Type {
constraintDeclaration := c.getConstraintDeclaration(t)
if constraintDeclaration != nil {
constraint = c.getTypeFromTypeNode(constraintDeclaration)
if constraint.flags&TypeFlagsAny != 0 && !c.isErrorType(constraint) {
isMappedTypeConstraint := ast.IsMappedTypeNode(constraintDeclaration.Parent.Parent)
if isMappedTypeConstraint && c.isTypeParameterReferenced(t, constraintDeclaration) {
// A mapped type parameter that is referenced in its own constraint, as in { [P in keyof P]: X }, has a
// circular constraint. Base constraint resolution doesn't reveal this because the base constraint of
// keyof P doesn't depend on P, so we check it here.
c.error(constraintDeclaration, diagnostics.Type_parameter_0_has_a_circular_constraint, c.TypeToString(t))
constraint = c.errorType
} else if constraint.flags&TypeFlagsAny != 0 && !c.isErrorType(constraint) {
// use stringNumberSymbolType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was),
// use unknown otherwise
if ast.IsMappedTypeNode(constraintDeclaration.Parent.Parent) {
if isMappedTypeConstraint {
constraint = c.stringNumberSymbolType
} else {
constraint = c.unknownType
Expand Down Expand Up @@ -22876,6 +22883,17 @@ func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bo
return true
}

// Return true if the given node contains a type reference that resolves to the given type parameter.
func (c *Checker) isTypeParameterReferenced(tp *Type, node *ast.Node) bool {
var containsReference func(*ast.Node) bool
containsReference = func(node *ast.Node) bool {
// Check the name first to avoid resolving unrelated type references
return isSimpleIdentifierTypeReference(node) && node.AsTypeReferenceNode().TypeName.Text() == tp.symbol.Name && c.getTypeFromTypeReference(node).symbol == tp.symbol ||
node.ForEachChild(containsReference)
}
return containsReference(node)
}

func (c *Checker) instantiateAnonymousType(t *Type, m *TypeMapper, alias *TypeAlias) *Type {
result := c.newObjectType(t.objectFlags&^(ObjectFlagsCouldContainTypeVariablesComputed|ObjectFlagsCouldContainTypeVariables)|ObjectFlagsInstantiated, t.symbol)
switch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
circularMappedTypeConstraint2.ts(4,11): error TS2313: Type parameter 'P' has a circular constraint.
circularMappedTypeConstraint2.ts(15,19): error TS2313: Type parameter 'P' has a circular constraint.
circularMappedTypeConstraint2.ts(16,19): error TS2313: Type parameter 'P' has a circular constraint.
circularMappedTypeConstraint2.ts(17,19): error TS2313: Type parameter 'P' has a circular constraint.
circularMappedTypeConstraint2.ts(18,22): error TS2313: Type parameter 'P' has a circular constraint.


==== circularMappedTypeConstraint2.ts (5 errors) ====
// https://github.com/microsoft/TypeScript/issues/62176

type Test = keyof {
[P in keyof P]: unknown // Error
~~~~~~~
!!! error TS2313: Type parameter 'P' has a circular constraint.
};

declare const test: Test;

export function getTest() {
return test;
}

type Cond<T> = T extends string ? { a: T } : { b: T };

type T1 = { [P in keyof P | "x"]: unknown }; // Error
~~~~~~~~~~~~~
!!! error TS2313: Type parameter 'P' has a circular constraint.
type T2 = { [P in Extract<keyof P, string>]: unknown }; // Error
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2313: Type parameter 'P' has a circular constraint.
type T3 = { [P in keyof Cond<P>]: unknown }; // Error
~~~~~~~~~~~~~
!!! error TS2313: Type parameter 'P' has a circular constraint.
type T4<T> = { [P in keyof P]: T }; // Error
~~~~~~~
!!! error TS2313: Type parameter 'P' has a circular constraint.

declare const t4: keyof T4<string>;

export function getT4() {
return t4;
}

// No errors

type T5<T extends keyof T> = T;
type T6 = { [P in keyof { [P in "a" | "b"]: P }]: P };
type T7<T> = { [P in keyof T as `get${P & string}`]: T[P] };

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//// [tests/cases/compiler/circularMappedTypeConstraint2.ts] ////

//// [circularMappedTypeConstraint2.ts]
// https://github.com/microsoft/TypeScript/issues/62176

type Test = keyof {
[P in keyof P]: unknown // Error
};

declare const test: Test;

export function getTest() {
return test;
}

type Cond<T> = T extends string ? { a: T } : { b: T };

type T1 = { [P in keyof P | "x"]: unknown }; // Error
type T2 = { [P in Extract<keyof P, string>]: unknown }; // Error
type T3 = { [P in keyof Cond<P>]: unknown }; // Error
type T4<T> = { [P in keyof P]: T }; // Error

declare const t4: keyof T4<string>;

export function getT4() {
return t4;
}

// No errors

type T5<T extends keyof T> = T;
type T6 = { [P in keyof { [P in "a" | "b"]: P }]: P };
type T7<T> = { [P in keyof T as `get${P & string}`]: T[P] };


//// [circularMappedTypeConstraint2.js]
// https://github.com/microsoft/TypeScript/issues/62176
export function getTest() {
return test;
}
export function getT4() {
return t4;
}


//// [circularMappedTypeConstraint2.d.ts]
export declare function getTest(): any;
export declare function getT4(): any;
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//// [tests/cases/compiler/circularMappedTypeConstraint2.ts] ////

=== circularMappedTypeConstraint2.ts ===
// https://github.com/microsoft/TypeScript/issues/62176

type Test = keyof {
>Test : Symbol(Test, Decl(circularMappedTypeConstraint2.ts, 0, 0))

[P in keyof P]: unknown // Error
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 3, 5))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 3, 5))

};

declare const test: Test;
>test : Symbol(test, Decl(circularMappedTypeConstraint2.ts, 6, 13))
>Test : Symbol(Test, Decl(circularMappedTypeConstraint2.ts, 0, 0))

export function getTest() {
>getTest : Symbol(getTest, Decl(circularMappedTypeConstraint2.ts, 6, 25))

return test;
>test : Symbol(test, Decl(circularMappedTypeConstraint2.ts, 6, 13))
}

type Cond<T> = T extends string ? { a: T } : { b: T };
>Cond : Symbol(Cond, Decl(circularMappedTypeConstraint2.ts, 10, 1))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 12, 10))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 12, 10))
>a : Symbol(a, Decl(circularMappedTypeConstraint2.ts, 12, 35))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 12, 10))
>b : Symbol(b, Decl(circularMappedTypeConstraint2.ts, 12, 46))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 12, 10))

type T1 = { [P in keyof P | "x"]: unknown }; // Error
>T1 : Symbol(T1, Decl(circularMappedTypeConstraint2.ts, 12, 54))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 14, 13))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 14, 13))

type T2 = { [P in Extract<keyof P, string>]: unknown }; // Error
>T2 : Symbol(T2, Decl(circularMappedTypeConstraint2.ts, 14, 44))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 15, 13))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 15, 13))

type T3 = { [P in keyof Cond<P>]: unknown }; // Error
>T3 : Symbol(T3, Decl(circularMappedTypeConstraint2.ts, 15, 55))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 16, 13))
>Cond : Symbol(Cond, Decl(circularMappedTypeConstraint2.ts, 10, 1))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 16, 13))

type T4<T> = { [P in keyof P]: T }; // Error
>T4 : Symbol(T4, Decl(circularMappedTypeConstraint2.ts, 16, 44))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 17, 8))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 17, 16))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 17, 16))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 17, 8))

declare const t4: keyof T4<string>;
>t4 : Symbol(t4, Decl(circularMappedTypeConstraint2.ts, 19, 13))
>T4 : Symbol(T4, Decl(circularMappedTypeConstraint2.ts, 16, 44))

export function getT4() {
>getT4 : Symbol(getT4, Decl(circularMappedTypeConstraint2.ts, 19, 35))

return t4;
>t4 : Symbol(t4, Decl(circularMappedTypeConstraint2.ts, 19, 13))
}

// No errors

type T5<T extends keyof T> = T;
>T5 : Symbol(T5, Decl(circularMappedTypeConstraint2.ts, 23, 1))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 27, 8))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 27, 8))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 27, 8))

type T6 = { [P in keyof { [P in "a" | "b"]: P }]: P };
>T6 : Symbol(T6, Decl(circularMappedTypeConstraint2.ts, 27, 31))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 28, 13))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 28, 27))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 28, 27))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 28, 13))

type T7<T> = { [P in keyof T as `get${P & string}`]: T[P] };
>T7 : Symbol(T7, Decl(circularMappedTypeConstraint2.ts, 28, 54))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 29, 8))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 29, 16))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 29, 8))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 29, 16))
>T : Symbol(T, Decl(circularMappedTypeConstraint2.ts, 29, 8))
>P : Symbol(P, Decl(circularMappedTypeConstraint2.ts, 29, 16))

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// [tests/cases/compiler/circularMappedTypeConstraint2.ts] ////

=== circularMappedTypeConstraint2.ts ===
// https://github.com/microsoft/TypeScript/issues/62176

type Test = keyof {
>Test : any

[P in keyof P]: unknown // Error
};

declare const test: Test;
>test : any

export function getTest() {
>getTest : () => any

return test;
>test : any
}

type Cond<T> = T extends string ? { a: T } : { b: T };
>Cond : Cond<T>
>a : T
>b : T

type T1 = { [P in keyof P | "x"]: unknown }; // Error
>T1 : T1

type T2 = { [P in Extract<keyof P, string>]: unknown }; // Error
>T2 : T2

type T3 = { [P in keyof Cond<P>]: unknown }; // Error
>T3 : T3

type T4<T> = { [P in keyof P]: T }; // Error
>T4 : T4<T>

declare const t4: keyof T4<string>;
>t4 : any

export function getT4() {
>getT4 : () => any

return t4;
>t4 : any
}

// No errors

type T5<T extends keyof T> = T;
>T5 : T

type T6 = { [P in keyof { [P in "a" | "b"]: P }]: P };
>T6 : T6

type T7<T> = { [P in keyof T as `get${P & string}`]: T[P] };
>T7 : T7<T>

32 changes: 32 additions & 0 deletions tsc/testdata/tests/cases/compiler/circularMappedTypeConstraint2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @declaration: true

// https://github.com/microsoft/TypeScript/issues/62176

type Test = keyof {
[P in keyof P]: unknown // Error
};

declare const test: Test;

export function getTest() {
return test;
}

type Cond<T> = T extends string ? { a: T } : { b: T };

type T1 = { [P in keyof P | "x"]: unknown }; // Error
type T2 = { [P in Extract<keyof P, string>]: unknown }; // Error
type T3 = { [P in keyof Cond<P>]: unknown }; // Error
type T4<T> = { [P in keyof P]: T }; // Error

declare const t4: keyof T4<string>;

export function getT4() {
return t4;
}

// No errors

type T5<T extends keyof T> = T;
type T6 = { [P in keyof { [P in "a" | "b"]: P }]: P };
type T7<T> = { [P in keyof T as `get${P & string}`]: T[P] };