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

[TS 4.5-dev] Regression: Argument of type is not assignable to parameter of type #46643

Closed
eamodio opened this issue Nov 2, 2021 · 3 comments
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@eamodio
Copy link

eamodio commented Nov 2, 2021

Bug Report

Getting lots of issues with a template string inference pattern I use in GitLens for creating strongly typed versions of string-based settings. And in TS 4.5-beta (and the currently nightly) I am getting Argument of type '* is not assignable to parameter of type errors with that pattern

🔎 Search Terms

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions 4.4.4 and 4.5-beta and still present in 4.5.0-dev.202111101
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
  • I was unable to test this on prior versions because _______

⏯ Playground Link

Playground link with relevant code

💻 Code

type SubPath<T, Key extends keyof T> = Key extends string
	? T[Key] extends Record<string, any>
		?
				| `${Key}.${SubPath<T[Key], Exclude<keyof T[Key], keyof any[]>> & string}`
				| `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}`
		: never
	: never;

type Path<T> = SubPath<T, keyof T> | keyof T extends string | keyof T ? SubPath<T, keyof T> | keyof T : keyof T;

type PathValue<T, P extends Path<T>> = P extends `${infer Key}.${infer Rest}`
	? Key extends keyof T
		? Rest extends Path<T[Key]>
			? PathValue<T[Key], Rest>
			: never
		: never
	: P extends keyof T
	? T[P]
	: never;


export interface Config {
	foo: {
		bar: {
			baz: boolean;
		};
	};
}

type ConfigPath = Path<Config>;
type ConfigPathValue<P extends ConfigPath> = PathValue<Config, P>;

export class Configuration {
	get(): Config;
	get<T extends ConfigPath>(
		section: T,
		defaultValue?: ConfigPathValue<T>,
	): ConfigPathValue<T>;
	get<T extends ConfigPath>(
		section?: T,
		defaultValue?: ConfigPathValue<T>,
	): Config | ConfigPathValue<T> {
		return undefined as any;
	}

}

const configuration = new Configuration();
const value = configuration.get('foo.bar.baz'); // <-- Argument of type '"foo.bar.baz"' is not assignable to parameter of type '"foo"' in TS 4.5-beta
Output
export class Configuration {
    get(section, defaultValue) {
        return undefined;
    }
}
const configuration = new Configuration();
const value = configuration.get('foo.bar.baz'); // <-- Argument of type '"foo.bar.baz"' is not assignable to parameter of type '"foo"' in TS 4.5-beta
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

🙁 Actual behavior

Argument of type '"foo.bar.baz"' is not assignable to parameter of type

🙂 Expected behavior

No errors like in TS 4.4.4 and prior

@andrewbranch
Copy link
Member

Can you explain your Path<T> type? When would the top-level condition not be true? Added parens for clarity:

(SubPath<T, keyof T> | keyof T) extends (string | keyof T)

To me, it looks like keyof T can be factored out of each side of the union, so you’re left with asking if SubPath<T, keyof T> is assignable to string. Every possible instantiation of SubPath is assignable to string, so I’m missing what this condition is supposed to be checking for.

Bizarrely, a workaround that would seem not to change the semantics of these types at all is to extract SubPath<T, keyof T> | keyof T to its own type alias. That definitely seems like a bug, but it’s hard for me to figure out how bad of a break this is because I can’t figure out the reason for the type at all. It seems to me like you could just as well define

type Path<T> = SubPath<T, keyof T> | keyof T;

and this example works as expected.

@ahejlsberg
Copy link
Member

ahejlsberg commented Nov 2, 2021

This appears to be another manifestation of #46624. The issue disappears from the main branch after this commit, so it should also be fixed in the release-4.5 branch after the cherry pick.

@eamodio
Copy link
Author

eamodio commented Nov 3, 2021

@andrewbranch I think I thought I needed that extra complexity on SubPath to allow for deeper nesting, but it looks like type Path<T> = SubPath<T, keyof T> | keyof T; as you suggested works just fine. Thanks!

eamodio added a commit to gitkraken/vscode-gitlens that referenced this issue Nov 3, 2021
Switches to use ansi-regex
Updates to Typescript 4.5-rc
Simplifies the SubPath type
See microsoft/TypeScript#46643 (comment)
@andrewbranch andrewbranch added Bug A bug in TypeScript Fixed A PR has been merged for this issue labels Nov 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

3 participants