Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected circular reference error TS7022 #50200

Open
jespertheend opened this issue Aug 5, 2022 · 2 comments
Open

Unexpected circular reference error TS7022 #50200

jespertheend opened this issue Aug 5, 2022 · 2 comments
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@jespertheend
Copy link
Contributor

Bug Report

πŸ”Ž Search Terms

TS7022

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about TS7022

⏯ Playground Link

πŸ’» Code

/** @template T */
class GenericClass {
	/**
	 * @param {(data: T) => void} handler
	 */
	setHandler(handler) {
	}
}

/** @type {GenericClass<typeof handlers>} */
const theClass = new GenericClass();
theClass.setHandler(() => {});

function emptyFunction() {}

const handlers = {
	thisWorksFine: emptyFunction,
	thisDoesnt: console.log,
};

πŸ™ Actual behavior

'theClass' is referenced directly or indirectly in its own type annotation, happens only in the JavaScript equivalent of this code and only if an external type is included in the handlers object.
Uncommenting thisDoesnt: console.log seems to remove the type errors for some reason.
theClass.setHandler(() => {}); also seems to be required for this

πŸ™‚ Expected behavior

No error, because there is no error in the TypeScript equivalent either.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Aug 5, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.9.0 milestone Aug 5, 2022
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Feb 1, 2023
@sandersn
Copy link
Member

sandersn commented May 8, 2023

This error occurs in the equivalent TS program:

class GenericClass<T> {
    setHandler(handler: (date: T) => void) {
    }
}

const theClass: GenericClass<typeof handlers> = new GenericClass();
theClass.setHandler(() => {});

function emptyFunction() {}

const handlers = {
	thisDoesnt: console.log,
};

Here's a version that doesn't rely on console.log to fail, and is a module, so shouldn't have other things in scope:

export class GenericClass<T> {
    setHandler() {
    }
}

declare const theClass: GenericClass<typeof handlers>;
theClass.setHandler()
declare var con: {
    new(): unknown;
};
const handlers = {
	thisDoesnt: con,
};

@sandersn
Copy link
Member

sandersn commented May 8, 2023

Here's the circularity. Everything is fine until checking the type of const handlers. Then this happens:

  • getTypeOfSymbol(handlers)
  • the type of handlers is derived from the type of its initialiser, part of which is con:
  • checkExpression(con)
  • after con's declared type is found, the compiler tries to narrow based on control flow:
  • getFlowTypeOfReference(con, { new(): unknown })
  • But this requires checking whether theClass.setHandler() has an asserts signature:
  • getTypeAtFlowCall(theClass.setHandler())
  • getEffectsSignature(theClass.setHandler())
  • Which of course means we need the type of theClass, and then typeof handlers, and then handlers:
  • getTypeOfSymbol(theClass)
  • getTypeFromTypeQueryNode(typeof handlers)
  • getTypeOfSymbol(handlers)

Here's a variation of the original program where the assertion should actually change the type of con:

export class C<T, U> {
    m(t: T, u: U): asserts u is U & { extra: "cool" } {
        if ((t as any).extra !== "cool") throw new Error("not cool");
    }
}

declare const theClass: C<typeof handlers, { new(): unknown }>;
declare var con: {
    new(): unknown;
};
theClass.m({ thisDoesnt: "matter", extra: "cool" });
const handlers = {
	thisDoesnt: con,
};

If I could figure out a way for con and handlers to be the same thing, then it would be a true circularity, although the current example still should be determinable by a sufficiently smart compiler (not present-day Typescript).

@sandersn sandersn added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels May 8, 2023
@sandersn sandersn modified the milestones: TypeScript 5.1.0, Backlog May 8, 2023
@sandersn sandersn removed their assignment May 8, 2023
@sandersn sandersn changed the title Incorrect TS7022 error in JavaScript Unexpected circular reference error TS7022 May 8, 2023
@RyanCavanaugh RyanCavanaugh added Help Wanted You can do this and removed Rescheduled This issue was previously scheduled to an earlier milestone labels May 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

No branches or pull requests

3 participants