Skip to content

Methods with an instantation expression can lead to a circular type annotation #57346

Closed as not planned
@DavidArchibald

Description

@DavidArchibald

🔎 Search Terms

Instantiation expression, referenced directly or indirectly in its own type annotation, circular type annotation, this, parent class base constraint.

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about referenced directly or directly in its own type annotation

⏯ Playground Link

https://www.typescriptlang.org/play?#code/MYGwhgzhAEDqCWAXAdgUygSQgBTAJ1WUQGFwoAeYAe2QkWioFd6BlKgW1VMhlQA9EhACYxcBItygA+aAG8AvgFgAUCtA9oYwiTIxZK6IegQOqBCnQQAFAHckaKAC5oiAJ4AHVFQBmce5awtCV1yRAALeAgpAEpnADcqeCE5JWVUtV1oFkYAI0leAWFRfG18uQMjE05zB2s7CycXDy9fGoCcEuCeUIio2OgEpLloVKMAejGjKamAPWgAcnra+ehI6AJvVHFgVGSheAJgRBBXBjxV5H3D49P4ZFXEGCobe7dPaDBkZCpEMER4GgAOisACYAKwABhB0RUqSAA

💻 Code

class WitnessIsParentClass<const out SomeClass extends ParentClass> {}

class ParentClass {
    someWitness(witness: typeof WitnessIsParentClass<this>): void {}
}

class SubClass extends ParentClass {
    someWitness(witness: typeof WitnessIsParentClass<this>): void { }
   //           ^ 'witness' is referenced directly or indirectly in its own type annotation.(2502)
}

🙁 Actual behavior

This errors with 'witness' is referenced directly or indirectly in its own type annotation.(2502)

🙂 Expected behavior

No error.

Additional information about the issue

If you change from typeof WitnessIsParentClass<this> to WitnessIsParentClass<this> the error goes away so this seems to be specific to instantiation expressions. I was hoping the variance annotation would give it enough information to resolve this problem but it didn't. I suppose that's probably because the variance annotations are only for the instance but not for the class as a whole.

This code example is obviously contrived but I ran into this while writing a complex mixin. I've actually ran into this style of error; 'x' is referenced directly or indirectly in its own type annotation.(2502) many times but this was the first time it was so easy to create a minimal example. I think the root cause in those cases may have been different in those cases however, so I should probably find a way to simplify them enough to share.

Interestingly if you add a level of indirection this works fine:

class WitnessIsParentClass<SomeClass extends ParentClass> {}

type WitnessHelper<SomeClass extends ParentClass> = typeof WitnessIsParentClass<SomeClass>

class ParentClass {
    someWitness(witness: WitnessHelper<this>): void {}
}

class SubClass extends ParentClass {
    someWitness(witness: WitnessHelper<this>): void { }
}

I assume this is because it essentially hoists the variance check and breaks the loop or something.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions