Microsoft/TypeScript

Resolution order dependent circular heritage

Open

#61,711 opened on May 16, 2025

View on GitHub
 (0 comments) (0 reactions) (0 assignees)TypeScript (48,455 stars) (6,726 forks)batch import
Domain: check: Type CircularityHelp WantedPossible Improvement

Description

🔎 Search Terms

resolution order dependent circular heritage, circular base type, circular mixin

🕗 Version & Regression Information

  • This changed in #39675

However, it appears this PR was the first time that circular heritage was (consistently) reported. Therefore this issue has essentially always existed.

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/PTAEDEEsA8FMBNSQGagJ4HsCuoDGGBbA2AOwBdQyALSAZ1ABtITZRsKMAnUWgd0jK4qlDKAAGAQVxkuABQAqAJQBMsMQDoAUPFi4GAQ06t8JWhWgAuUAElTZfSVyx5aAA6wAPGTewMqKTKcCiqwAHwA3JpRIKAAqrQIIjxUGLyUVPocJAxooPqgOu4kOo65GCTi8mJI9CxOtLSGuchc6caQnLhYBpwCaFoxEvTMADRtoMjM+gygRrQYDABuiYvTWKzwGLC1GBQEmWSwnFre7qAAIrBF8LQA8iQe8qGgALx5JGiR2ro9xgYNoACrQA3ppQKBXJwMK4rJdrncHgFIMsAKLIZC6MgAbQARJDoTiALoRTQAXyiOj0hlYJH0xForn0ThshwIoFB4JiUDgiBQ6GwPH4gmE1DoSQcSHIR2QTNgWnBp1Y1gIrgYsGI5EykHKrxsdgcThc7g85wwXQ1ZAAwv9aJbypMAOa46ysokk8nfKlGPA2lnq9lg0CWC5XUg3e4eJGo9GY3H41xuyIerkwRJ8zA4IwEDDLcZiADKWAARka1KAHaQjpBcBDDHTYIdjpoqQCgUElKoPIWSz5dQ40M9YNBDsV6G32cmwNy06gM7N1TnWNRWAXi6XqhWWL0a4zOPXG1oW-QXergp3u6W+x9B8Ow8fWROosxGzLmabzaQrTa7SRHQHOVOqa8rOApCA4FbiIqfiAtIcgdmWMiQT40Ftmenj9qEYiBm2VhQf4sHtiEXwARAQFICBOBgSQEFiHhfoEGh1SIbRyGoCeDHwR4GFYeC7G4ax9FoUmFI-NSPr6K20jIrAaIYtIoBDiONygNakCfu+WAWgAsjAzAABQAEISbAUYyTG0gAJT-hCUIwqAJCaUWRzCZ6vwTFgjhkNqFSqepZqaZ+OnQMwHiBimPLkfyOAMBgGAJBUy54OUZh7s+bCoGIRkJNaEm0BogZZbAOUAopd72bAaR6eo1WGA6tBWP2WKElZLzPBgRYAFaYiMmihIZxlWIVxW0BZg3GcNXyUm5R6gIVpmyZi1kmClWAEVVNWcHVsKhqOEbseoyqquqn5auUoQWUmQA

💻 Code

// Fixed if you comment this line out or switch to `ActorPTR2e`.
declare const x: InstanceType<typeof ActorPTR2e>;


// Used to show that only a dependency on `T` is necessary for the circularity.
// As in, the final resolved value does not matter.
type DependsOn<T> = any;

declare class Actor {
  prop: DependsOn<ActiveEffect["prop"]>;
}

declare namespace Item {
  // Fixed if you switch this to an interface.
  type Implementation = InstanceType<DocumentClassConfig["Item"]>;
}

declare class Item {
  x: DependsOn<ActiveEffect["prop"]>;
}

// Fixed if you remove the `SubType` generic parameter.
class ActorPTR2e<SubType = any> extends Actor {}

// Fixed if you remove the `SubType` generic parameter.
class ItemPTR2e<SubType = any> extends Item {}

interface DocumentClassConfig {
  // Fixed if you change `typeof ActorPTR2e` to `typeof ActorPTR2e<any>`
  Actor: typeof ActorPTR2e;

  // Fixed if you change `typeof ItemPTR2e` to `typeof ItemPTR2e<any>`
  Item: typeof ItemPTR2e;
}

declare class ActiveEffect extends ClientDocumentMixin(BaseActiveEffect) {
  prop: number;
}

declare function ClientDocumentMixin<
  // Fixed if you loosen the constraint of `BaseClass`.
  BaseClass extends new (...args: any[]) => object,
>(Base: BaseClass): BaseClass;

declare class BaseActiveEffect {
  constructor(...args: DependsOn<Item.Implementation>);
}

🙁 Actual behavior

Numerous errors, namely:

Type alias 'Implementation' circularly references itself.
Type 'ActiveEffect' recursively references itself as a base type.
'ActiveEffect' is referenced directly or indirectly in its own base expression.
'args' is referenced directly or indirectly in its own type annotation.

🙂 Expected behavior

No error, as seen when you comment out the line declare const x: InstanceType<typeof ActorPTR2e>;. The error going away indicates that this circularity is resolution order dependent.

I understand how odd the code is out of context but I did actually run into this in a real codebase.

Additional information about the issue

No response

Contributor guide