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

Improve contextual typing of ending tuple elements #53036

Merged
merged 7 commits into from
Mar 19, 2023
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
62 changes: 44 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29079,18 +29079,45 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return undefined;
}

// In an array literal contextually typed by a type T, the contextual type of an element expression at index N is
// the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature,
// it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated
// type of T.
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
return arrayContextualType && (
index >= 0 && getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) ||
mapType(arrayContextualType, t =>
isTupleType(t) ?
getElementTypeOfSliceOfTupleType(t, 0, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true) :
getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
/*noReductions*/ true));
function getSpreadIndices(elements: readonly Node[]) {
let first, last;
for (let i = 0; i < elements.length; i++) {
if (isSpreadElement(elements[i])) {
first ??= i;
last = i;
}
}
return { first, last };
}

function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined {
return type && mapType(type, t => {
if (isTupleType(t)) {
// If index is before any spread element and within the fixed part of the contextual tuple type, return
// the type of the contextual tuple element.
if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) {
return getTypeArguments(t)[index];
}
// When the length is known and the index is after all spread elements we compute the offset from the element
// to the end and the number of ending fixed elements in the contextual tuple type.
const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0;
const fixedEndLength = offset > 0 && t.target.hasRestElement ? getEndElementCount(t.target, ElementFlags.Fixed) : 0;
// If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual
// tuple element.
if (offset > 0 && offset <= fixedEndLength) {
return getTypeArguments(t)[getTypeReferenceArity(t) - offset];
}
// Return a union of the possible contextual element types with no subtype reduction.
return getElementTypeOfSliceOfTupleType(t,
firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex),
length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex),
/*writing*/ false, /*noReductions*/ true);
}
// If element index is known and a contextual property with that name exists, return it. Otherwise return the
// iterated or element type of the contextual type.
return (!firstSpreadIndex || index < firstSpreadIndex) && getTypeOfPropertyOfContextualType(t, "" + index as __String) ||
getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false);
}, /*noReductions*/ true);
}

// In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type.
Expand Down Expand Up @@ -29321,12 +29348,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ArrayLiteralExpression: {
const arrayLiteral = parent as ArrayLiteralExpression;
const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
// The index of an array literal element doesn't necessarily line up with the index of the corresponding
// element in a contextual tuple type when there are preceding spread elements in the array literal. For
// this reason we only pass indices for elements that precede the first spread element.
const spreadIndex = getNodeLinks(arrayLiteral).firstSpreadIndex ??= findIndex(arrayLiteral.elements, isSpreadElement);
const elementIndex = indexOfNode(arrayLiteral.elements, node);
return getContextualTypeForElementExpression(type, spreadIndex < 0 || elementIndex < spreadIndex ? elementIndex : -1);
const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements);
return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last);
}
case SyntaxKind.ConditionalExpression:
return getContextualTypeForConditionalOperand(node, contextFlags);
Expand Down Expand Up @@ -32321,7 +32345,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
else {
const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual);
const contextualType = isTupleType(restType) ?
getContextualTypeForElementExpression(restType, i - index, argCount - index) || unknownType :
getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual);
const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode);
const hasPrimitiveContextualType = inConstContext || maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping);
types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType));
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6028,7 +6028,7 @@ export interface NodeLinks {
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime.
firstSpreadIndex?: number; // Index of first spread element in array literal (-1 for none)
spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain.
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/compiler/contextualSignatureInArrayElementPrefersArrayUnionMemberLibEs2015.ts(8,4): error TS7006: Parameter 'arg' implicitly has an 'any' type.


==== tests/cases/compiler/contextualSignatureInArrayElementPrefersArrayUnionMemberLibEs2015.ts (1 errors) ====
// repro from #52588

declare function test(
arg: Record<string, (arg: string) => void> | Array<(arg: number) => void>
): void;

test([
(arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg; // number
},
]);

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/compiler/contextualSignatureInArrayElementPrefersArrayUnionMemberLibEs5.ts(8,4): error TS7006: Parameter 'arg' implicitly has an 'any' type.


==== tests/cases/compiler/contextualSignatureInArrayElementPrefersArrayUnionMemberLibEs5.ts (1 errors) ====
// repro from #52588

declare function test(
arg: Record<string, (arg: string) => void> | Array<(arg: number) => void>
): void;

test([
(arg) => {
~~~
!!! error TS7006: Parameter 'arg' implicitly has an 'any' type.
arg; // number
},
]);

72 changes: 72 additions & 0 deletions tests/baselines/reference/contextualTypeTupleEnd.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts(8,1): error TS2345: Argument of type '[]' is not assignable to parameter of type '[...((arg: number) => void)[], (arg: string) => void]'.
Source has 0 element(s) but target requires 1.
tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts(13,7): error TS2322: Type '[]' is not assignable to type 'Funcs'.
Source has 0 element(s) but target requires 1.
tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts(43,12): error TS2339: Property 'foo' does not exist on type 'number'.
tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts(44,12): error TS2339: Property 'bar' does not exist on type 'number'.


==== tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts (4 errors) ====
type Funcs = [...((arg: number) => void)[], (arg: string) => void];

declare function num(x: number): void;
declare function str(x: string): void;

declare function f1(...args: Funcs): void;

f1(); // Error
~~~~
!!! error TS2345: Argument of type '[]' is not assignable to parameter of type '[...((arg: number) => void)[], (arg: string) => void]'.
!!! error TS2345: Source has 0 element(s) but target requires 1.
f1(x => str(x));
f1(x => num(x), x => str(x));
f1(x => num(x), x => num(x), x => str(x));

const a0: Funcs = []; // Error
~~
!!! error TS2322: Type '[]' is not assignable to type 'Funcs'.
!!! error TS2322: Source has 0 element(s) but target requires 1.
const a1: Funcs = [x => str(x)];
const a2: Funcs = [x => num(x), x => str(x)];
const a3: Funcs = [x => num(x), x => num(x), x => str(x)];

// Repro from #43122

export type Selector<State> = (state: State) => unknown;
export type SelectorTuple<State> = Selector<State>[];

export type ExampleState = {
foo: "foo";
bar: 42;
};

export function createSelector<S extends SelectorTuple<ExampleState>>(...selectors: [...selectors: S, f: (x: any) => any]) {
console.log(selectors);
}

createSelector(
x => x.foo,
x => x.bar,
() => 42
);

// Repro from #43122

declare function example(...args: [...((n: number) => void)[], (x: any) => void]): void

example(
x => x.foo, // Error
~~~
!!! error TS2339: Property 'foo' does not exist on type 'number'.
x => x.bar, // Error
~~~
!!! error TS2339: Property 'bar' does not exist on type 'number'.
x => x.baz,
);

// Repro from #52846

declare function test(...args: [...((arg: number) => void)[], (arg: string) => void]): void;

test(a => a, b => b, c => c);

184 changes: 184 additions & 0 deletions tests/baselines/reference/contextualTypeTupleEnd.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
=== tests/cases/conformance/types/tuple/contextualTypeTupleEnd.ts ===
type Funcs = [...((arg: number) => void)[], (arg: string) => void];
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))
>arg : Symbol(arg, Decl(contextualTypeTupleEnd.ts, 0, 19))
>arg : Symbol(arg, Decl(contextualTypeTupleEnd.ts, 0, 45))

declare function num(x: number): void;
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 2, 21))

declare function str(x: string): void;
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 3, 21))

declare function f1(...args: Funcs): void;
>f1 : Symbol(f1, Decl(contextualTypeTupleEnd.ts, 3, 38))
>args : Symbol(args, Decl(contextualTypeTupleEnd.ts, 5, 20))
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))

f1(); // Error
>f1 : Symbol(f1, Decl(contextualTypeTupleEnd.ts, 3, 38))

f1(x => str(x));
>f1 : Symbol(f1, Decl(contextualTypeTupleEnd.ts, 3, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 8, 3))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 8, 3))

f1(x => num(x), x => str(x));
>f1 : Symbol(f1, Decl(contextualTypeTupleEnd.ts, 3, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 9, 3))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 9, 3))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 9, 15))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 9, 15))

f1(x => num(x), x => num(x), x => str(x));
>f1 : Symbol(f1, Decl(contextualTypeTupleEnd.ts, 3, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 3))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 3))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 15))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 15))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 28))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 10, 28))

const a0: Funcs = []; // Error
>a0 : Symbol(a0, Decl(contextualTypeTupleEnd.ts, 12, 5))
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))

const a1: Funcs = [x => str(x)];
>a1 : Symbol(a1, Decl(contextualTypeTupleEnd.ts, 13, 5))
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 13, 19))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 13, 19))

const a2: Funcs = [x => num(x), x => str(x)];
>a2 : Symbol(a2, Decl(contextualTypeTupleEnd.ts, 14, 5))
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 14, 19))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 14, 19))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 14, 31))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 14, 31))

const a3: Funcs = [x => num(x), x => num(x), x => str(x)];
>a3 : Symbol(a3, Decl(contextualTypeTupleEnd.ts, 15, 5))
>Funcs : Symbol(Funcs, Decl(contextualTypeTupleEnd.ts, 0, 0))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 19))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 19))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 31))
>num : Symbol(num, Decl(contextualTypeTupleEnd.ts, 0, 67))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 31))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 44))
>str : Symbol(str, Decl(contextualTypeTupleEnd.ts, 2, 38))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 15, 44))

// Repro from #43122

export type Selector<State> = (state: State) => unknown;
>Selector : Symbol(Selector, Decl(contextualTypeTupleEnd.ts, 15, 58))
>State : Symbol(State, Decl(contextualTypeTupleEnd.ts, 19, 21))
>state : Symbol(state, Decl(contextualTypeTupleEnd.ts, 19, 31))
>State : Symbol(State, Decl(contextualTypeTupleEnd.ts, 19, 21))

export type SelectorTuple<State> = Selector<State>[];
>SelectorTuple : Symbol(SelectorTuple, Decl(contextualTypeTupleEnd.ts, 19, 56))
>State : Symbol(State, Decl(contextualTypeTupleEnd.ts, 20, 26))
>Selector : Symbol(Selector, Decl(contextualTypeTupleEnd.ts, 15, 58))
>State : Symbol(State, Decl(contextualTypeTupleEnd.ts, 20, 26))

export type ExampleState = {
>ExampleState : Symbol(ExampleState, Decl(contextualTypeTupleEnd.ts, 20, 53))

foo: "foo";
>foo : Symbol(foo, Decl(contextualTypeTupleEnd.ts, 22, 28))

bar: 42;
>bar : Symbol(bar, Decl(contextualTypeTupleEnd.ts, 23, 15))

};

export function createSelector<S extends SelectorTuple<ExampleState>>(...selectors: [...selectors: S, f: (x: any) => any]) {
>createSelector : Symbol(createSelector, Decl(contextualTypeTupleEnd.ts, 25, 2))
>S : Symbol(S, Decl(contextualTypeTupleEnd.ts, 27, 31))
>SelectorTuple : Symbol(SelectorTuple, Decl(contextualTypeTupleEnd.ts, 19, 56))
>ExampleState : Symbol(ExampleState, Decl(contextualTypeTupleEnd.ts, 20, 53))
>selectors : Symbol(selectors, Decl(contextualTypeTupleEnd.ts, 27, 70))
>S : Symbol(S, Decl(contextualTypeTupleEnd.ts, 27, 31))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 27, 106))

console.log(selectors);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>selectors : Symbol(selectors, Decl(contextualTypeTupleEnd.ts, 27, 70))
}

createSelector(
>createSelector : Symbol(createSelector, Decl(contextualTypeTupleEnd.ts, 25, 2))

x => x.foo,
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 31, 15))
>x.foo : Symbol(foo, Decl(contextualTypeTupleEnd.ts, 22, 28))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 31, 15))
>foo : Symbol(foo, Decl(contextualTypeTupleEnd.ts, 22, 28))

x => x.bar,
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 32, 15))
>x.bar : Symbol(bar, Decl(contextualTypeTupleEnd.ts, 23, 15))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 32, 15))
>bar : Symbol(bar, Decl(contextualTypeTupleEnd.ts, 23, 15))

() => 42
);

// Repro from #43122

declare function example(...args: [...((n: number) => void)[], (x: any) => void]): void
>example : Symbol(example, Decl(contextualTypeTupleEnd.ts, 35, 2))
>args : Symbol(args, Decl(contextualTypeTupleEnd.ts, 39, 25))
>n : Symbol(n, Decl(contextualTypeTupleEnd.ts, 39, 40))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 39, 64))

example(
>example : Symbol(example, Decl(contextualTypeTupleEnd.ts, 35, 2))

x => x.foo, // Error
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 41, 8))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 41, 8))

x => x.bar, // Error
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 42, 15))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 42, 15))

x => x.baz,
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 43, 15))
>x : Symbol(x, Decl(contextualTypeTupleEnd.ts, 43, 15))

);

// Repro from #52846

declare function test(...args: [...((arg: number) => void)[], (arg: string) => void]): void;
>test : Symbol(test, Decl(contextualTypeTupleEnd.ts, 45, 2))
>args : Symbol(args, Decl(contextualTypeTupleEnd.ts, 49, 22))
>arg : Symbol(arg, Decl(contextualTypeTupleEnd.ts, 49, 37))
>arg : Symbol(arg, Decl(contextualTypeTupleEnd.ts, 49, 63))

test(a => a, b => b, c => c);
>test : Symbol(test, Decl(contextualTypeTupleEnd.ts, 45, 2))
>a : Symbol(a, Decl(contextualTypeTupleEnd.ts, 51, 5))
>a : Symbol(a, Decl(contextualTypeTupleEnd.ts, 51, 5))
>b : Symbol(b, Decl(contextualTypeTupleEnd.ts, 51, 12))
>b : Symbol(b, Decl(contextualTypeTupleEnd.ts, 51, 12))
>c : Symbol(c, Decl(contextualTypeTupleEnd.ts, 51, 20))
>c : Symbol(c, Decl(contextualTypeTupleEnd.ts, 51, 20))

Loading