Skip to content
Draft
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
29 changes: 22 additions & 7 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22354,6 +22354,23 @@ func (c *Checker) getObjectTypeInstantiation(t *Type, m *TypeMapper, alias *Type
}

func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bool {
var tpDeclaration *ast.Node
if tp.symbol != nil {
declarations := tp.symbol.Declarations
switch {
case len(declarations) == 1:
tpDeclaration = declarations[0]
case tp.AsTypeParameter().isThisType && core.Some(declarations, ast.IsClassDeclaration):
// A class/interface merge gives polymorphic `this` a symbol with multiple
// declarations. Select the declaration that actually contains this node.
for _, declaration := range declarations {
if isNodeDescendantOf(node, declaration) {
tpDeclaration = declaration
break
}
}
}
}
var containsReference func(*ast.Node) bool
containsReference = func(node *ast.Node) bool {
switch node.Kind {
Expand All @@ -22369,7 +22386,6 @@ func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bo
firstIdentifier := ast.GetFirstIdentifier(entityName)
if !ast.IsThisIdentifier(firstIdentifier) {
firstIdentifierSymbol := c.getResolvedSymbol(firstIdentifier)
tpDeclaration := tp.symbol.Declarations[0] // There is exactly one declaration, otherwise `containsReference` is not called
var tpScope *ast.Node
switch {
case ast.IsTypeParameterDeclaration(tpDeclaration):
Expand All @@ -22392,12 +22408,11 @@ func (c *Checker) isTypeParameterPossiblyReferenced(tp *Type, node *ast.Node) bo
}
return node.ForEachChild(containsReference)
}
// If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks
// between the node and the type parameter declaration, if the node contains actual references to the
// type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter,
// we consider the type parameter possibly referenced.
if tp.symbol != nil && len(tp.symbol.Declarations) == 1 {
container := tp.symbol.Declarations[0].Parent
// For regular type parameters with multiple declarations, or a polymorphic `this` whose containing
// declaration can't be identified, conservatively assume a reference. We do the same when there are
// intervening statement blocks, actual references, or type queries whose references can't be disproved.
if tpDeclaration != nil {
container := tpDeclaration.Parent
for n := node; n != container; n = n.Parent {
if n == nil || ast.IsBlock(n) || ast.IsConditionalTypeNode(n) && containsReference(n.AsConditionalTypeNode().ExtendsType) {
return true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
selfReferenceExactInstancesPreserveFiniteNesting.ts(50,5): error TS2322: Type 'Right.Outer' is not assignable to type 'Left.Outer'.
Types of property 'box' are incompatible.
Type 'Box<Right.L2>' is not assignable to type 'Box<Left.L2>'.
Type 'Right.L2' is not assignable to type 'Left.L2'.
Types of property 'box' are incompatible.
Type 'Box<Right.L3>' is not assignable to type 'Box<Left.L3>'.
Type 'Right.L3' is not assignable to type 'Left.L3'.
Types of property 'box' are incompatible.
Type 'Box<Right.Leaf>' is not assignable to type 'Box<Left.Leaf>'.
Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
Types of property 'id' are incompatible.
Type 'number' is not assignable to type 'string'.


==== selfReferenceExactInstancesPreserveFiniteNesting.ts (1 errors) ====
interface Box<T> {
left2(): Box<Left.L2>;
left3(): Box<Left.L3>;
leftLeaf(): Box<Left.Leaf>;
right2(): Box<Right.L2>;
right3(): Box<Right.L3>;
rightLeaf(): Box<Right.Leaf>;
value: T;
}

namespace Left {
export interface Outer {
box: Box<L2>;
}
export interface L2 {
box: Box<L3>;
}
export interface L3 {
box: Box<Leaf>;
}
export interface Leaf {
id: string;
}
}

namespace Right {
export interface Outer {
box: Box<L2>;
}
export interface L2 {
box: Box<L3>;
}
export interface L3 {
box: Box<Leaf>;
}
export interface Leaf {
id: number;
}
}

declare const box: Box<unknown>;
box.left2();
box.left3();
box.leftLeaf();
box.right2();
box.right3();
box.rightLeaf();

function test(left: Left.Outer, right: Right.Outer) {
left = right;
~~~~
!!! error TS2322: Type 'Right.Outer' is not assignable to type 'Left.Outer'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'Box<Right.L2>' is not assignable to type 'Box<Left.L2>'.
!!! error TS2322: Type 'Right.L2' is not assignable to type 'Left.L2'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'Box<Right.L3>' is not assignable to type 'Box<Left.L3>'.
!!! error TS2322: Type 'Right.L3' is not assignable to type 'Left.L3'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'Box<Right.Leaf>' is not assignable to type 'Box<Left.Leaf>'.
!!! error TS2322: Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
!!! error TS2322: Types of property 'id' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
selfReferenceInMethodBody.ts(5,15): error TS2322: Type 'Box<Box<Box<number>>>' is not assignable to type 'Box<Box<Box<string>>>'.
Type 'Box<Box<number>>' is not assignable to type 'Box<Box<string>>'.
Type 'Box<number>' is not assignable to type 'Box<string>'.
Type 'number' is not assignable to type 'string'.


==== selfReferenceInMethodBody.ts (1 errors) ====
class Box<T> {
value!: T;

method(source: Box<Box<Box<number>>>) {
const target: Box<Box<Box<string>>> = source;
~~~~~~
!!! error TS2322: Type 'Box<Box<Box<number>>>' is not assignable to type 'Box<Box<Box<string>>>'.
!!! error TS2322: Type 'Box<Box<number>>' is not assignable to type 'Box<Box<string>>'.
!!! error TS2322: Type 'Box<number>' is not assignable to type 'Box<string>'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
selfReferencePreservesFiniteNesting.ts(37,5): error TS2322: Type 'Right.Outer' is not assignable to type 'Left.Outer'.
Types of property 'box' are incompatible.
Type 'RecursiveBox<Right.Inner>' is not assignable to type 'RecursiveBox<Left.Inner>'.
Type 'Right.Inner' is not assignable to type 'Left.Inner'.
Types of property 'box' are incompatible.
Type 'RecursiveBox<Right.Mid>' is not assignable to type 'RecursiveBox<Left.Mid>'.
Type 'Right.Mid' is not assignable to type 'Left.Mid'.
Types of property 'box' are incompatible.
Type 'RecursiveBox<Right.Leaf>' is not assignable to type 'RecursiveBox<Left.Leaf>'.
Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
Types of property 'id' are incompatible.
Type 'number' is not assignable to type 'string'.


==== selfReferencePreservesFiniteNesting.ts (1 errors) ====
interface RecursiveBox<T> {
aNext?: RecursiveBox<T>;
zValue: T;
}

namespace Left {
export interface Outer {
box: RecursiveBox<Inner>;
}
export interface Inner {
box: RecursiveBox<Mid>;
}
export interface Mid {
box: RecursiveBox<Leaf>;
}
export interface Leaf {
id: string;
}
}

namespace Right {
export interface Outer {
box: RecursiveBox<Inner>;
}
export interface Inner {
box: RecursiveBox<Mid>;
}
export interface Mid {
box: RecursiveBox<Leaf>;
}
export interface Leaf {
id: number;
}
}

function test(left: Left.Outer, right: Right.Outer) {
left = right;
~~~~
!!! error TS2322: Type 'Right.Outer' is not assignable to type 'Left.Outer'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'RecursiveBox<Right.Inner>' is not assignable to type 'RecursiveBox<Left.Inner>'.
!!! error TS2322: Type 'Right.Inner' is not assignable to type 'Left.Inner'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'RecursiveBox<Right.Mid>' is not assignable to type 'RecursiveBox<Left.Mid>'.
!!! error TS2322: Type 'Right.Mid' is not assignable to type 'Left.Mid'.
!!! error TS2322: Types of property 'box' are incompatible.
!!! error TS2322: Type 'RecursiveBox<Right.Leaf>' is not assignable to type 'RecursiveBox<Left.Leaf>'.
!!! error TS2322: Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
!!! error TS2322: Types of property 'id' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
typePredicateRecursionPreservesFiniteNesting.ts(22,7): error TS2322: Type '() => this is Guard<Right.L2> & Right.L2' is not assignable to type '() => this is Guard<Left.L2> & Left.L2'.
Type predicate 'this is Guard<L2> & L2' is not assignable to 'this is Guard<L2> & L2'.
Type 'Guard<Right.L2> & Right.L2' is not assignable to type 'Guard<Left.L2> & Left.L2'.
Type 'Guard<L2> & L2' is not assignable to type 'Guard<L2>'.
Types of property 'guard' are incompatible.
Type '() => this is Guard<Right.L2> & Right.L2' is not assignable to type '() => this is Guard<Left.L2> & Left.L2'.
Type predicate 'this is Guard<L2> & L2' is not assignable to 'this is Guard<L2> & L2'.
Type 'Guard<Right.L2> & Right.L2' is not assignable to type 'Guard<Left.L2> & Left.L2'.
Type 'Guard<L2> & L2' is not assignable to type 'L2'.
Types of property 'next' are incompatible.
Type 'Guard<Right.L3>' is not assignable to type 'Guard<Left.L3>'.
Type 'Right.L3' is not assignable to type 'Left.L3'.
Types of property 'next' are incompatible.
Type 'Guard<Right.L4>' is not assignable to type 'Guard<Left.L4>'.
Type 'Right.L4' is not assignable to type 'Left.L4'.
Types of property 'next' are incompatible.
Type 'Guard<Right.Leaf>' is not assignable to type 'Guard<Left.Leaf>'.
Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
Types of property 'id' are incompatible.
Type 'number' is not assignable to type 'string'.
typePredicateRecursionPreservesFiniteNesting.ts(23,7): error TS2322: Type 'Guard<Right.L2>' is not assignable to type 'Guard<Left.L2>'.
Type 'Right.L2' is not assignable to type 'Left.L2'.
Types of property 'next' are incompatible.
Type 'Guard<Right.L3>' is not assignable to type 'Guard<Left.L3>'.
Type 'Right.L3' is not assignable to type 'Left.L3'.
Types of property 'next' are incompatible.
Type 'Guard<Right.L4>' is not assignable to type 'Guard<Left.L4>'.
Type 'Right.L4' is not assignable to type 'Left.L4'.
Types of property 'next' are incompatible.
Type 'Guard<Right.Leaf>' is not assignable to type 'Guard<Left.Leaf>'.
Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
Types of property 'id' are incompatible.
Type 'number' is not assignable to type 'string'.


==== typePredicateRecursionPreservesFiniteNesting.ts (2 errors) ====
interface Guard<T> {
guard(): this is this & T;
}

namespace Left {
export interface L2 { next: Guard<L3>; }
export interface L3 { next: Guard<L4>; }
export interface L4 { next: Guard<Leaf>; }
export interface Leaf { id: string; }
}

namespace Right {
export interface L2 { next: Guard<L3>; }
export interface L3 { next: Guard<L4>; }
export interface L4 { next: Guard<Leaf>; }
export interface Leaf { id: number; }
}

declare const left: Guard<Left.L2>;
declare const right: Guard<Right.L2>;

const predicateComparison: typeof left.guard = right.guard;
~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type '() => this is Guard<Right.L2> & Right.L2' is not assignable to type '() => this is Guard<Left.L2> & Left.L2'.
!!! error TS2322: Type predicate 'this is Guard<L2> & L2' is not assignable to 'this is Guard<L2> & L2'.
!!! error TS2322: Type 'Guard<Right.L2> & Right.L2' is not assignable to type 'Guard<Left.L2> & Left.L2'.
!!! error TS2322: Type 'Guard<L2> & L2' is not assignable to type 'Guard<L2>'.
!!! error TS2322: Types of property 'guard' are incompatible.
!!! error TS2322: Type '() => this is Guard<Right.L2> & Right.L2' is not assignable to type '() => this is Guard<Left.L2> & Left.L2'.
!!! error TS2322: Type predicate 'this is Guard<L2> & L2' is not assignable to 'this is Guard<L2> & L2'.
!!! error TS2322: Type 'Guard<Right.L2> & Right.L2' is not assignable to type 'Guard<Left.L2> & Left.L2'.
!!! error TS2322: Type 'Guard<L2> & L2' is not assignable to type 'L2'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.L3>' is not assignable to type 'Guard<Left.L3>'.
!!! error TS2322: Type 'Right.L3' is not assignable to type 'Left.L3'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.L4>' is not assignable to type 'Guard<Left.L4>'.
!!! error TS2322: Type 'Right.L4' is not assignable to type 'Left.L4'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.Leaf>' is not assignable to type 'Guard<Left.Leaf>'.
!!! error TS2322: Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
!!! error TS2322: Types of property 'id' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
const ordinaryComparison: Guard<Left.L2> = right;
~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'Guard<Right.L2>' is not assignable to type 'Guard<Left.L2>'.
!!! error TS2322: Type 'Right.L2' is not assignable to type 'Left.L2'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.L3>' is not assignable to type 'Guard<Left.L3>'.
!!! error TS2322: Type 'Right.L3' is not assignable to type 'Left.L3'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.L4>' is not assignable to type 'Guard<Left.L4>'.
!!! error TS2322: Type 'Right.L4' is not assignable to type 'Left.L4'.
!!! error TS2322: Types of property 'next' are incompatible.
!!! error TS2322: Type 'Guard<Right.Leaf>' is not assignable to type 'Guard<Left.Leaf>'.
!!! error TS2322: Type 'Right.Leaf' is not assignable to type 'Left.Leaf'.
!!! error TS2322: Types of property 'id' are incompatible.
!!! error TS2322: Type 'number' is not assignable to type 'string'.

33 changes: 33 additions & 0 deletions testdata/tests/cases/compiler/mergedPolymorphicThisReferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// @noEmit: true
// @noTypesAndSymbols: true
// @strict: true

// @filename: class.ts
declare class MergedBase {}

// @filename: interface.ts
type Owned<T> = { owner: T };

interface MergedBase {
clone(): this;
isOwned(): this is this & Owned<this>;
queried(value: this, box: { value: typeof value }): void;
conditional(): this extends MergedDerived ? "derived" : "base";
}

declare class MergedDerived extends MergedBase {
derived: true;
}

declare const base: MergedBase;
declare const derived: MergedDerived;

const cloned: MergedDerived = derived.clone();
const conditionalValue: "derived" = derived.conditional();

// @ts-expect-error `typeof value` must be instantiated from MergedBase to MergedDerived.
derived.queried(derived, { value: base });

if (derived.isOwned()) {
const owner: MergedDerived = derived.owner;
}
Loading