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

Get apparent type for calls used directly at argument positions to aid nested inferences #52866

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
31 changes: 18 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31042,6 +31042,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}

function getDiscriminatedApparentType(node: Expression | MethodDeclaration, type: Type) {
const apparentType = mapType(
type,
// When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types.
// That would evaluate mapped types with array or tuple type constraints too eagerly
// and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions.
// Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types.
t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t),
/*noReductions*/ true,
);
return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) :
apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) :
apparentType;
}

// Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily
// be "pushed" onto a node using the contextualType property.
function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined {
Expand All @@ -31050,18 +31065,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
getContextualType(node, contextFlags);
const instantiatedType = instantiateContextualType(contextualType, node, contextFlags);
if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) {
const apparentType = mapType(
instantiatedType,
// When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types.
// That would evaluate mapped types with array or tuple type constraints too eagerly
// and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions.
// Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types.
t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t),
/*noReductions*/ true,
);
return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) :
apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) :
apparentType;
return getDiscriminatedApparentType(node, instantiatedType);
}
}

Expand Down Expand Up @@ -34163,7 +34167,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// outer call expression. Effectively we just want a snapshot of whatever has been
// inferred for any outer call expression so far.
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault));
const instantiatedType = instantiateType(contextualType, outerMapper);
const argumentApparentType = outerContext && isCallOrNewExpression(node.parent) && getDiscriminatedApparentType(node, contextualType);
const instantiatedType = instantiateType(argumentApparentType && argumentApparentType !== unknownType ? argumentApparentType : contextualType, outerMapper);
// If the contextual type is a generic function type with a single call signature, we
// instantiate the type with its own type parameters and type arguments. This ensures that
// the type parameters are not erased to type any during type inference such that they can
Expand Down
81 changes: 81 additions & 0 deletions tests/baselines/reference/nestedInference.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
nestedInference.ts(36,15): error TS2345: Argument of type '"this is wrong"' is not assignable to parameter of type 'Pattern<"a" | "b">'.
nestedInference.ts(50,15): error TS2345: Argument of type '"this is wrong"' is not assignable to parameter of type 'Pattern<"a" | "b">'.


==== nestedInference.ts (2 errors) ====
// repro from #52864

const matcher = Symbol("@ts-pattern/matcher");

type MatcherProtocol<input> = {
match: <I>(value: I | input) => void;
};

interface Matcher<input> {
[matcher](): MatcherProtocol<input>;
}

type Pattern<a> =
| Matcher<a>
| (a extends readonly [any, ...any]
? { readonly [index in keyof a]: Pattern<a[index]> }
: a extends object
? { readonly [k in keyof a]: Pattern<a[k]> }
: a);

type Match<i> = {
with<p extends Pattern<i>>(pattern: p): void;
};

declare function match<input>(value: input): Match<input>;
declare function union<input, ps extends [Pattern<input>, ...Pattern<input>[]]>(
...patterns: ps
): Matcher<input>;
declare function when<input, p extends (value: input) => unknown>(
predicate: p
): Matcher<input>;

match<"a" | "b">("a").with(union("a"));

match<"a" | "b">("a")
.with(union("this is wrong")); // error
~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '"this is wrong"' is not assignable to parameter of type 'Pattern<"a" | "b">'.

match<"a" | "b">("a").with(
when((x) => {
let a: "a" | "b" = x; // OK
return a;
})
);

match<{ type: "a" | "b" }>({ type: "a" }).with({
type: union("a"),
});

match<{ type: "a" | "b" }>({ type: "a" }).with({
type: union("this is wrong"), // error
~~~~~~~~~~~~~~~
!!! error TS2345: Argument of type '"this is wrong"' is not assignable to parameter of type 'Pattern<"a" | "b">'.
});

match<{ type: "a" | "b" }>({ type: "a" }).with({
type: when((x) => {
let a: "a" | "b" = x; // OK
return a;
}),
});

// https://github.com/microsoft/TypeScript/issues/57978
interface Action<Value> {
_run(input: Value): Value;
}

declare function minLength<Value extends string | any[]>(min: number): Action<Value>

declare function pipe1<TAction1 extends Action<string>>(
action1: TAction1,
): (input: string) => string;

const Schema1 = pipe1(minLength(1));

235 changes: 235 additions & 0 deletions tests/baselines/reference/nestedInference.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
//// [tests/cases/compiler/nestedInference.ts] ////

=== nestedInference.ts ===
// repro from #52864

const matcher = Symbol("@ts-pattern/matcher");
>matcher : Symbol(matcher, Decl(nestedInference.ts, 2, 5))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))

type MatcherProtocol<input> = {
>MatcherProtocol : Symbol(MatcherProtocol, Decl(nestedInference.ts, 2, 46))
>input : Symbol(input, Decl(nestedInference.ts, 4, 21))

match: <I>(value: I | input) => void;
>match : Symbol(match, Decl(nestedInference.ts, 4, 31))
>I : Symbol(I, Decl(nestedInference.ts, 5, 10))
>value : Symbol(value, Decl(nestedInference.ts, 5, 13))
>I : Symbol(I, Decl(nestedInference.ts, 5, 10))
>input : Symbol(input, Decl(nestedInference.ts, 4, 21))

};

interface Matcher<input> {
>Matcher : Symbol(Matcher, Decl(nestedInference.ts, 6, 2))
>input : Symbol(input, Decl(nestedInference.ts, 8, 18))

[matcher](): MatcherProtocol<input>;
>[matcher] : Symbol(Matcher[matcher], Decl(nestedInference.ts, 8, 26))
>matcher : Symbol(matcher, Decl(nestedInference.ts, 2, 5))
>MatcherProtocol : Symbol(MatcherProtocol, Decl(nestedInference.ts, 2, 46))
>input : Symbol(input, Decl(nestedInference.ts, 8, 18))
}

type Pattern<a> =
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))

| Matcher<a>
>Matcher : Symbol(Matcher, Decl(nestedInference.ts, 6, 2))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))

| (a extends readonly [any, ...any]
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))

? { readonly [index in keyof a]: Pattern<a[index]> }
>index : Symbol(index, Decl(nestedInference.ts, 15, 20))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))
>index : Symbol(index, Decl(nestedInference.ts, 15, 20))

: a extends object
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))

? { readonly [k in keyof a]: Pattern<a[k]> }
>k : Symbol(k, Decl(nestedInference.ts, 17, 20))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))
>k : Symbol(k, Decl(nestedInference.ts, 17, 20))

: a);
>a : Symbol(a, Decl(nestedInference.ts, 12, 13))

type Match<i> = {
>Match : Symbol(Match, Decl(nestedInference.ts, 18, 11))
>i : Symbol(i, Decl(nestedInference.ts, 20, 11))

with<p extends Pattern<i>>(pattern: p): void;
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>p : Symbol(p, Decl(nestedInference.ts, 21, 7))
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>i : Symbol(i, Decl(nestedInference.ts, 20, 11))
>pattern : Symbol(pattern, Decl(nestedInference.ts, 21, 29))
>p : Symbol(p, Decl(nestedInference.ts, 21, 7))

};

declare function match<input>(value: input): Match<input>;
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>input : Symbol(input, Decl(nestedInference.ts, 24, 23))
>value : Symbol(value, Decl(nestedInference.ts, 24, 30))
>input : Symbol(input, Decl(nestedInference.ts, 24, 23))
>Match : Symbol(Match, Decl(nestedInference.ts, 18, 11))
>input : Symbol(input, Decl(nestedInference.ts, 24, 23))

declare function union<input, ps extends [Pattern<input>, ...Pattern<input>[]]>(
>union : Symbol(union, Decl(nestedInference.ts, 24, 58))
>input : Symbol(input, Decl(nestedInference.ts, 25, 23))
>ps : Symbol(ps, Decl(nestedInference.ts, 25, 29))
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>input : Symbol(input, Decl(nestedInference.ts, 25, 23))
>Pattern : Symbol(Pattern, Decl(nestedInference.ts, 10, 1))
>input : Symbol(input, Decl(nestedInference.ts, 25, 23))

...patterns: ps
>patterns : Symbol(patterns, Decl(nestedInference.ts, 25, 80))
>ps : Symbol(ps, Decl(nestedInference.ts, 25, 29))

): Matcher<input>;
>Matcher : Symbol(Matcher, Decl(nestedInference.ts, 6, 2))
>input : Symbol(input, Decl(nestedInference.ts, 25, 23))

declare function when<input, p extends (value: input) => unknown>(
>when : Symbol(when, Decl(nestedInference.ts, 27, 18))
>input : Symbol(input, Decl(nestedInference.ts, 28, 22))
>p : Symbol(p, Decl(nestedInference.ts, 28, 28))
>value : Symbol(value, Decl(nestedInference.ts, 28, 40))
>input : Symbol(input, Decl(nestedInference.ts, 28, 22))

predicate: p
>predicate : Symbol(predicate, Decl(nestedInference.ts, 28, 66))
>p : Symbol(p, Decl(nestedInference.ts, 28, 28))

): Matcher<input>;
>Matcher : Symbol(Matcher, Decl(nestedInference.ts, 6, 2))
>input : Symbol(input, Decl(nestedInference.ts, 28, 22))

match<"a" | "b">("a").with(union("a"));
>match<"a" | "b">("a").with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>union : Symbol(union, Decl(nestedInference.ts, 24, 58))

match<"a" | "b">("a")
>match<"a" | "b">("a") .with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))

.with(union("this is wrong")); // error
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>union : Symbol(union, Decl(nestedInference.ts, 24, 58))

match<"a" | "b">("a").with(
>match<"a" | "b">("a").with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))

when((x) => {
>when : Symbol(when, Decl(nestedInference.ts, 27, 18))
>x : Symbol(x, Decl(nestedInference.ts, 38, 8))

let a: "a" | "b" = x; // OK
>a : Symbol(a, Decl(nestedInference.ts, 39, 7))
>x : Symbol(x, Decl(nestedInference.ts, 38, 8))

return a;
>a : Symbol(a, Decl(nestedInference.ts, 39, 7))

})
);

match<{ type: "a" | "b" }>({ type: "a" }).with({
>match<{ type: "a" | "b" }>({ type: "a" }).with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>type : Symbol(type, Decl(nestedInference.ts, 44, 7))
>type : Symbol(type, Decl(nestedInference.ts, 44, 28))
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))

type: union("a"),
>type : Symbol(type, Decl(nestedInference.ts, 44, 48))
>union : Symbol(union, Decl(nestedInference.ts, 24, 58))

});

match<{ type: "a" | "b" }>({ type: "a" }).with({
>match<{ type: "a" | "b" }>({ type: "a" }).with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>type : Symbol(type, Decl(nestedInference.ts, 48, 7))
>type : Symbol(type, Decl(nestedInference.ts, 48, 28))
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))

type: union("this is wrong"), // error
>type : Symbol(type, Decl(nestedInference.ts, 48, 48))
>union : Symbol(union, Decl(nestedInference.ts, 24, 58))

});

match<{ type: "a" | "b" }>({ type: "a" }).with({
>match<{ type: "a" | "b" }>({ type: "a" }).with : Symbol(with, Decl(nestedInference.ts, 20, 17))
>match : Symbol(match, Decl(nestedInference.ts, 22, 2))
>type : Symbol(type, Decl(nestedInference.ts, 52, 7))
>type : Symbol(type, Decl(nestedInference.ts, 52, 28))
>with : Symbol(with, Decl(nestedInference.ts, 20, 17))

type: when((x) => {
>type : Symbol(type, Decl(nestedInference.ts, 52, 48))
>when : Symbol(when, Decl(nestedInference.ts, 27, 18))
>x : Symbol(x, Decl(nestedInference.ts, 53, 14))

let a: "a" | "b" = x; // OK
>a : Symbol(a, Decl(nestedInference.ts, 54, 7))
>x : Symbol(x, Decl(nestedInference.ts, 53, 14))

return a;
>a : Symbol(a, Decl(nestedInference.ts, 54, 7))

}),
});

// https://github.com/microsoft/TypeScript/issues/57978
interface Action<Value> {
>Action : Symbol(Action, Decl(nestedInference.ts, 57, 3))
>Value : Symbol(Value, Decl(nestedInference.ts, 60, 17))

_run(input: Value): Value;
>_run : Symbol(Action._run, Decl(nestedInference.ts, 60, 25))
>input : Symbol(input, Decl(nestedInference.ts, 61, 7))
>Value : Symbol(Value, Decl(nestedInference.ts, 60, 17))
>Value : Symbol(Value, Decl(nestedInference.ts, 60, 17))
}

declare function minLength<Value extends string | any[]>(min: number): Action<Value>
>minLength : Symbol(minLength, Decl(nestedInference.ts, 62, 1))
>Value : Symbol(Value, Decl(nestedInference.ts, 64, 27))
>min : Symbol(min, Decl(nestedInference.ts, 64, 57))
>Action : Symbol(Action, Decl(nestedInference.ts, 57, 3))
>Value : Symbol(Value, Decl(nestedInference.ts, 64, 27))

declare function pipe1<TAction1 extends Action<string>>(
>pipe1 : Symbol(pipe1, Decl(nestedInference.ts, 64, 84))
>TAction1 : Symbol(TAction1, Decl(nestedInference.ts, 66, 23))
>Action : Symbol(Action, Decl(nestedInference.ts, 57, 3))

action1: TAction1,
>action1 : Symbol(action1, Decl(nestedInference.ts, 66, 56))
>TAction1 : Symbol(TAction1, Decl(nestedInference.ts, 66, 23))

): (input: string) => string;
>input : Symbol(input, Decl(nestedInference.ts, 68, 4))

const Schema1 = pipe1(minLength(1));
>Schema1 : Symbol(Schema1, Decl(nestedInference.ts, 70, 5))
>pipe1 : Symbol(pipe1, Decl(nestedInference.ts, 64, 84))
>minLength : Symbol(minLength, Decl(nestedInference.ts, 62, 1))

Loading