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

Fix inferences between alias type arguments and defaulted alias type arguments #51771

Merged
Merged
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
14 changes: 11 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20763,7 +20763,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (variances === emptyArray) {
return Ternary.Unknown;
}
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
const params = getSymbolLinks(source.aliasSymbol).typeParameters!;
const minParams = getMinTypeArgumentCount(params);
const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
const varianceResult = relateVariances(sourceTypes, targetTypes, variances, intersectionState);
if (varianceResult !== undefined) {
return varianceResult;
}
Expand Down Expand Up @@ -23698,8 +23702,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (source.aliasSymbol && source.aliasSymbol === target.aliasSymbol) {
if (source.aliasTypeArguments) {
// Source and target are types originating in the same generic type alias declaration.
// Simply infer from source type arguments to target type arguments.
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
// Simply infer from source type arguments to target type arguments, with defaults applied.
const params = getSymbolLinks(source.aliasSymbol).typeParameters!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I wasn't even aware that the type argument list could be incomplete at this point, and I'm not sure that was ever intended. It's fine for type arguments to be missing at the lexical level, but once we're dealing with types we pretty much assume that the length of a type argument list matches the length of the corresponding type parameter list. That assumption is pretty pervasive throughout the compiler (modulo the this type argument that sometimes isn't present). I think it would make more sense to find the origin of the incomplete list and put the call to fillMissingTypeArguments there.

Copy link
Member Author

@weswigham weswigham Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so, our old logic for giving an alias symbol instantiation an alias symbol didn't fill missing type arguments, which is why the new condition I added also didn't - it didn't seem required, since our existing usage didn't fill them. Obviously, broader usage of these unfilled lists reveals the flaw, namely inference (and probably comparison checking) not going well, since, as you said, they currently expect filled lists. Thing is, I think trafficking the unfilled lists has an advantage - it may be worth handling incomplete lists in inference/comparison checking. If we start filling them eagerly, rather than getting the user-supplied list, when we printback, we don't printback the user supplied list anymore. Meaning, even though you wrote Component, we print back and serialize Component<{}, any, any>. For complex defaults, this means we can muddy the printback unnecessarily quite a bit. So while I could fix this by passing in filled lists at the now 2 locations we supply an alias for an alias symbol instantiation, the UX is much nicer if instead we update inference and comparison checking to handle incomplete type alias argument lists, imo.

const minParams = getMinTypeArgumentCount(params);
const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration));
jakebailey marked this conversation as resolved.
Show resolved Hide resolved
inferFromTypeArguments(sourceTypes, targetTypes!, getAliasVariances(source.aliasSymbol));
}
// And if there weren't any type arguments, there's no reason to run inference as the types must be the same.
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//// [tests/cases/compiler/importedAliasedConditionalTypeInstantiation.ts] ////

//// [index.d.ts]
export type Handler<TEvent = any, TResult = any> = (
event: TEvent,
context: {},
callback: Callback<TResult>,
) => void | Promise<TResult>;

export type Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void;

//// [index.d.ts]
import { Handler, Callback } from 'aws-lambda';
declare namespace lambdaTester {
type HandlerEvent<T extends Handler> = T extends Handler<infer TEvent> ? TEvent : never;
type HandlerResult<T extends Handler> = T extends Handler<any, infer TResult> ? TResult : never;
type HandlerError<T extends Handler> = T extends Handler<any, infer TResult>
? NonNullable<Parameters<Callback<TResult>>['0']>
: never;

interface VerifierFn<S> {
(result: S, additional?: any): void | Promise<void>;
(result: S, additional?: any, done?: () => {}): void;
}
type Verifier<S> = S extends HandlerError<Handler>
? S extends string
? VerifierFn<string>
: S extends Error
? VerifierFn<Error>
: never
: VerifierFn<S>;

class LambdaTester<T extends Handler> {
event(event: HandlerEvent<T>): this;
}
}

declare function lambdaTester<T extends Handler>(handler: T): lambdaTester.LambdaTester<T>;

export = lambdaTester;
//// [index.ts]
import * as lambdaTester from 'lambda-tester';
import { Handler } from 'aws-lambda';

type Actual = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler>>;
type Expected = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler<any, any>>>;

//// [index.js]
"use strict";
exports.__esModule = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
=== tests/cases/compiler/node_modules/aws-lambda/index.d.ts ===
export type Handler<TEvent = any, TResult = any> = (
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 0))
>TEvent : Symbol(TEvent, Decl(index.d.ts, 0, 20))
>TResult : Symbol(TResult, Decl(index.d.ts, 0, 33))

event: TEvent,
>event : Symbol(event, Decl(index.d.ts, 0, 52))
>TEvent : Symbol(TEvent, Decl(index.d.ts, 0, 20))

context: {},
>context : Symbol(context, Decl(index.d.ts, 1, 18))

callback: Callback<TResult>,
>callback : Symbol(callback, Decl(index.d.ts, 2, 16))
>Callback : Symbol(Callback, Decl(index.d.ts, 4, 29))
>TResult : Symbol(TResult, Decl(index.d.ts, 0, 33))

) => void | Promise<TResult>;
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
>TResult : Symbol(TResult, Decl(index.d.ts, 0, 33))

export type Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void;
>Callback : Symbol(Callback, Decl(index.d.ts, 4, 29))
>TResult : Symbol(TResult, Decl(index.d.ts, 6, 21))
>error : Symbol(error, Decl(index.d.ts, 6, 39))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>result : Symbol(result, Decl(index.d.ts, 6, 69))
>TResult : Symbol(TResult, Decl(index.d.ts, 6, 21))

=== tests/cases/compiler/node_modules/lambda-tester/index.d.ts ===
import { Handler, Callback } from 'aws-lambda';
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>Callback : Symbol(Callback, Decl(index.d.ts, 0, 17))

declare namespace lambdaTester {
>lambdaTester : Symbol(lambdaTester, Decl(index.d.ts, 23, 1), Decl(index.d.ts, 0, 47))

type HandlerEvent<T extends Handler> = T extends Handler<infer TEvent> ? TEvent : never;
>HandlerEvent : Symbol(HandlerEvent, Decl(index.d.ts, 1, 32))
>T : Symbol(T, Decl(index.d.ts, 2, 22))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>T : Symbol(T, Decl(index.d.ts, 2, 22))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>TEvent : Symbol(TEvent, Decl(index.d.ts, 2, 66))
>TEvent : Symbol(TEvent, Decl(index.d.ts, 2, 66))

type HandlerResult<T extends Handler> = T extends Handler<any, infer TResult> ? TResult : never;
>HandlerResult : Symbol(HandlerResult, Decl(index.d.ts, 2, 92))
>T : Symbol(T, Decl(index.d.ts, 3, 23))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>T : Symbol(T, Decl(index.d.ts, 3, 23))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>TResult : Symbol(TResult, Decl(index.d.ts, 3, 72))
>TResult : Symbol(TResult, Decl(index.d.ts, 3, 72))

type HandlerError<T extends Handler> = T extends Handler<any, infer TResult>
>HandlerError : Symbol(HandlerError, Decl(index.d.ts, 3, 100))
>T : Symbol(T, Decl(index.d.ts, 4, 22))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>T : Symbol(T, Decl(index.d.ts, 4, 22))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>TResult : Symbol(TResult, Decl(index.d.ts, 4, 71))

? NonNullable<Parameters<Callback<TResult>>['0']>
>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>Callback : Symbol(Callback, Decl(index.d.ts, 0, 17))
>TResult : Symbol(TResult, Decl(index.d.ts, 4, 71))

: never;

interface VerifierFn<S> {
>VerifierFn : Symbol(VerifierFn, Decl(index.d.ts, 6, 16))
>S : Symbol(S, Decl(index.d.ts, 8, 25))

(result: S, additional?: any): void | Promise<void>;
>result : Symbol(result, Decl(index.d.ts, 9, 9))
>S : Symbol(S, Decl(index.d.ts, 8, 25))
>additional : Symbol(additional, Decl(index.d.ts, 9, 19))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))

(result: S, additional?: any, done?: () => {}): void;
>result : Symbol(result, Decl(index.d.ts, 10, 9))
>S : Symbol(S, Decl(index.d.ts, 8, 25))
>additional : Symbol(additional, Decl(index.d.ts, 10, 19))
>done : Symbol(done, Decl(index.d.ts, 10, 37))
}
type Verifier<S> = S extends HandlerError<Handler>
>Verifier : Symbol(Verifier, Decl(index.d.ts, 11, 5))
>S : Symbol(S, Decl(index.d.ts, 12, 18))
>S : Symbol(S, Decl(index.d.ts, 12, 18))
>HandlerError : Symbol(HandlerError, Decl(index.d.ts, 3, 100))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))

? S extends string
>S : Symbol(S, Decl(index.d.ts, 12, 18))

? VerifierFn<string>
>VerifierFn : Symbol(VerifierFn, Decl(index.d.ts, 6, 16))

: S extends Error
>S : Symbol(S, Decl(index.d.ts, 12, 18))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

? VerifierFn<Error>
>VerifierFn : Symbol(VerifierFn, Decl(index.d.ts, 6, 16))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

: never
: VerifierFn<S>;
>VerifierFn : Symbol(VerifierFn, Decl(index.d.ts, 6, 16))
>S : Symbol(S, Decl(index.d.ts, 12, 18))

class LambdaTester<T extends Handler> {
>LambdaTester : Symbol(LambdaTester, Decl(index.d.ts, 18, 24))
>T : Symbol(T, Decl(index.d.ts, 20, 23))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))

event(event: HandlerEvent<T>): this;
>event : Symbol(LambdaTester.event, Decl(index.d.ts, 20, 43))
>event : Symbol(event, Decl(index.d.ts, 21, 14))
>HandlerEvent : Symbol(HandlerEvent, Decl(index.d.ts, 1, 32))
>T : Symbol(T, Decl(index.d.ts, 20, 23))
}
}

declare function lambdaTester<T extends Handler>(handler: T): lambdaTester.LambdaTester<T>;
>lambdaTester : Symbol(lambdaTester, Decl(index.d.ts, 23, 1), Decl(index.d.ts, 0, 47))
>T : Symbol(T, Decl(index.d.ts, 25, 30))
>Handler : Symbol(Handler, Decl(index.d.ts, 0, 8))
>handler : Symbol(handler, Decl(index.d.ts, 25, 49))
>T : Symbol(T, Decl(index.d.ts, 25, 30))
>lambdaTester : Symbol(lambdaTester, Decl(index.d.ts, 23, 1), Decl(index.d.ts, 0, 47))
>LambdaTester : Symbol(lambdaTester.LambdaTester, Decl(index.d.ts, 18, 24))
>T : Symbol(T, Decl(index.d.ts, 25, 30))

export = lambdaTester;
>lambdaTester : Symbol(lambdaTester, Decl(index.d.ts, 23, 1), Decl(index.d.ts, 0, 47))

=== tests/cases/compiler/index.ts ===
import * as lambdaTester from 'lambda-tester';
>lambdaTester : Symbol(lambdaTester, Decl(index.ts, 0, 6))

import { Handler } from 'aws-lambda';
>Handler : Symbol(Handler, Decl(index.ts, 1, 8))

type Actual = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler>>;
>Actual : Symbol(Actual, Decl(index.ts, 1, 37))
>lambdaTester : Symbol(lambdaTester, Decl(index.ts, 0, 6))
>Verifier : Symbol(lambdaTester.Verifier, Decl(index.d.ts, 11, 5))
>lambdaTester : Symbol(lambdaTester, Decl(index.ts, 0, 6))
>HandlerResult : Symbol(lambdaTester.HandlerResult, Decl(index.d.ts, 2, 92))
>Handler : Symbol(Handler, Decl(index.ts, 1, 8))

type Expected = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler<any, any>>>;
>Expected : Symbol(Expected, Decl(index.ts, 3, 73))
>lambdaTester : Symbol(lambdaTester, Decl(index.ts, 0, 6))
>Verifier : Symbol(lambdaTester.Verifier, Decl(index.d.ts, 11, 5))
>lambdaTester : Symbol(lambdaTester, Decl(index.ts, 0, 6))
>HandlerResult : Symbol(lambdaTester.HandlerResult, Decl(index.d.ts, 2, 92))
>Handler : Symbol(Handler, Decl(index.ts, 1, 8))

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
=== tests/cases/compiler/node_modules/aws-lambda/index.d.ts ===
export type Handler<TEvent = any, TResult = any> = (
>Handler : Handler<TEvent, TResult>

event: TEvent,
>event : TEvent

context: {},
>context : {}

callback: Callback<TResult>,
>callback : Callback<TResult>

) => void | Promise<TResult>;

export type Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void;
>Callback : Callback<TResult>
>error : string | Error
>null : null
>result : TResult

=== tests/cases/compiler/node_modules/lambda-tester/index.d.ts ===
import { Handler, Callback } from 'aws-lambda';
>Handler : any
>Callback : any

declare namespace lambdaTester {
>lambdaTester : typeof lambdaTester

type HandlerEvent<T extends Handler> = T extends Handler<infer TEvent> ? TEvent : never;
>HandlerEvent : HandlerEvent<T>

type HandlerResult<T extends Handler> = T extends Handler<any, infer TResult> ? TResult : never;
>HandlerResult : HandlerResult<T>

type HandlerError<T extends Handler> = T extends Handler<any, infer TResult>
>HandlerError : HandlerError<T>

? NonNullable<Parameters<Callback<TResult>>['0']>
: never;

interface VerifierFn<S> {
(result: S, additional?: any): void | Promise<void>;
>result : S
>additional : any

(result: S, additional?: any, done?: () => {}): void;
>result : S
>additional : any
>done : () => {}
}
type Verifier<S> = S extends HandlerError<Handler>
>Verifier : Verifier<S>

? S extends string
? VerifierFn<string>
: S extends Error
? VerifierFn<Error>
: never
: VerifierFn<S>;

class LambdaTester<T extends Handler> {
>LambdaTester : LambdaTester<T>

event(event: HandlerEvent<T>): this;
>event : (event: HandlerEvent<T>) => this
>event : HandlerEvent<T>
}
}

declare function lambdaTester<T extends Handler>(handler: T): lambdaTester.LambdaTester<T>;
>lambdaTester : typeof lambdaTester
>handler : T
>lambdaTester : any

export = lambdaTester;
>lambdaTester : typeof lambdaTester

=== tests/cases/compiler/index.ts ===
import * as lambdaTester from 'lambda-tester';
>lambdaTester : typeof lambdaTester

import { Handler } from 'aws-lambda';
>Handler : any

type Actual = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler>>;
>Actual : lambdaTester.VerifierFn<string> | lambdaTester.VerifierFn<Error> | lambdaTester.VerifierFn<any>
>lambdaTester : any
>lambdaTester : any

type Expected = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler<any, any>>>;
>Expected : lambdaTester.VerifierFn<string> | lambdaTester.VerifierFn<Error> | lambdaTester.VerifierFn<any>
>lambdaTester : any
>lambdaTester : any

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// @filename: node_modules/aws-lambda/index.d.ts
export type Handler<TEvent = any, TResult = any> = (
event: TEvent,
context: {},
callback: Callback<TResult>,
) => void | Promise<TResult>;

export type Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void;

// @filename: node_modules/lambda-tester/index.d.ts
import { Handler, Callback } from 'aws-lambda';
declare namespace lambdaTester {
type HandlerEvent<T extends Handler> = T extends Handler<infer TEvent> ? TEvent : never;
type HandlerResult<T extends Handler> = T extends Handler<any, infer TResult> ? TResult : never;
type HandlerError<T extends Handler> = T extends Handler<any, infer TResult>
? NonNullable<Parameters<Callback<TResult>>['0']>
: never;

interface VerifierFn<S> {
(result: S, additional?: any): void | Promise<void>;
(result: S, additional?: any, done?: () => {}): void;
}
type Verifier<S> = S extends HandlerError<Handler>
? S extends string
? VerifierFn<string>
: S extends Error
? VerifierFn<Error>
: never
: VerifierFn<S>;

class LambdaTester<T extends Handler> {
event(event: HandlerEvent<T>): this;
}
}

declare function lambdaTester<T extends Handler>(handler: T): lambdaTester.LambdaTester<T>;

export = lambdaTester;
// @filename: index.ts

import * as lambdaTester from 'lambda-tester';
import { Handler } from 'aws-lambda';

type Actual = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler>>;
type Expected = lambdaTester.Verifier<lambdaTester.HandlerResult<Handler<any, any>>>;