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 contextual typing for post-spread tuple elements #52769

Merged
merged 7 commits into from
Feb 18, 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
26 changes: 16 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23074,7 +23074,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return restType && createArrayType(restType);
}

function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) {
function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = false) {
const length = getTypeReferenceArity(type) - endSkipCount;
if (index < length) {
const typeArguments = getTypeArguments(type);
Expand All @@ -23083,7 +23083,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const t = typeArguments[i];
elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t);
}
return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes);
return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes, noReductions ? UnionReduction.None : UnionReduction.Literal);
}
return undefined;
}
Expand Down Expand Up @@ -28829,9 +28829,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (prop) {
return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop);
}
if (isTupleType(t)) {
const restType = getRestTypeOfTupleType(t);
if (restType && isNumericLiteralName(name) && +name >= 0) {
if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) {
const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true);
if (restType) {
return restType;
}
}
Expand Down Expand Up @@ -28883,10 +28883,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// type of T.
function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined {
return arrayContextualType && (
getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String)
|| mapType(
arrayContextualType,
t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false),
index >= 0 && getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

I know that it's not exactly 100% related to this case - but I was touching this function just recently here. It would be cool if that improvement (if correct) could also go into 5.0 - both would make for a nice "batch" of improvements for contextual types for element expressions. 😉

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));
}

Expand Down Expand Up @@ -29118,7 +29119,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ArrayLiteralExpression: {
const arrayLiteral = parent as ArrayLiteralExpression;
const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags);
return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node));
// 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);
}
case SyntaxKind.ConditionalExpression:
return getContextualTypeForConditionalOperand(node, contextFlags);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5997,6 +5997,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)
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
}

Expand Down
139 changes: 139 additions & 0 deletions tests/baselines/reference/spreadsAndContextualTupleTypes.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
=== tests/cases/compiler/spreadsAndContextualTupleTypes.ts ===
declare function fx1<T extends [string, string, string, 'a' | 'b']>(x: T): T;
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))
>x : Symbol(x, Decl(spreadsAndContextualTupleTypes.ts, 0, 68))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 0, 21))

declare function fx2<T extends [...string[], 'a' | 'b']>(x: T): T;
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))
>x : Symbol(x, Decl(spreadsAndContextualTupleTypes.ts, 1, 57))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 1, 21))

const t3 = ['x', 'y', 'z'] as const;
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))
>const : Symbol(const)

fx1(['x', 'y', 'z', 'a']);
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))

fx1([...t3, 'a']);
>fx1 : Symbol(fx1, Decl(spreadsAndContextualTupleTypes.ts, 0, 0))
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))

fx2(['x', 'y', 'z', 'a']);
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))

fx2([...t3, 'a']);
>fx2 : Symbol(fx2, Decl(spreadsAndContextualTupleTypes.ts, 0, 77))
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))

const x1: [...string[], '!'] = ['!'];
>x1 : Symbol(x1, Decl(spreadsAndContextualTupleTypes.ts, 11, 5))

const x2: [...string[], '!'] = ['a', '!'];
>x2 : Symbol(x2, Decl(spreadsAndContextualTupleTypes.ts, 12, 5))

const x3: [...string[], '!'] = [...t3, '!'];
>x3 : Symbol(x3, Decl(spreadsAndContextualTupleTypes.ts, 13, 5))
>t3 : Symbol(t3, Decl(spreadsAndContextualTupleTypes.ts, 3, 5))

// Repro from #52684

const staticPath1Level = ["home"] as const;
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
>const : Symbol(const)

const staticPath2Level = ["home", "user"] as const;
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
>const : Symbol(const)

const staticPath3Level = ["home", "user", "downloads"] as const;
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
>const : Symbol(const)

const randomID = 'id' as string;
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

declare function foo<const T>(path: T): T;
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))
>path : Symbol(path, Decl(spreadsAndContextualTupleTypes.ts, 23, 30))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))
>T : Symbol(T, Decl(spreadsAndContextualTupleTypes.ts, 23, 21))

const a1 = foo([...staticPath1Level, randomID, 'doc.pdf']);
>a1 : Symbol(a1, Decl(spreadsAndContextualTupleTypes.ts, 25, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const a2 = foo([...staticPath2Level, randomID, 'doc.pdf']);
>a2 : Symbol(a2, Decl(spreadsAndContextualTupleTypes.ts, 26, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const a3 = foo([...staticPath3Level, randomID, 'doc.pdf']);
>a3 : Symbol(a3, Decl(spreadsAndContextualTupleTypes.ts, 27, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const b1 = foo([...staticPath1Level, randomID, 'folder', 'doc.pdf']);
>b1 : Symbol(b1, Decl(spreadsAndContextualTupleTypes.ts, 29, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const b2 = foo([...staticPath2Level, randomID, 'folder', 'doc.pdf']);
>b2 : Symbol(b2, Decl(spreadsAndContextualTupleTypes.ts, 30, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const b3 = foo([...staticPath3Level, randomID, 'folder', 'doc.pdf']);
>b3 : Symbol(b3, Decl(spreadsAndContextualTupleTypes.ts, 31, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const c1 = foo([...staticPath1Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
>c1 : Symbol(c1, Decl(spreadsAndContextualTupleTypes.ts, 33, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const c2 = foo([...staticPath2Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
>c2 : Symbol(c2, Decl(spreadsAndContextualTupleTypes.ts, 34, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const c3 = foo([...staticPath3Level, randomID, 'folder', 'subfolder', 'doc.pdf']);
>c3 : Symbol(c3, Decl(spreadsAndContextualTupleTypes.ts, 35, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const d1 = foo([...staticPath1Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
>d1 : Symbol(d1, Decl(spreadsAndContextualTupleTypes.ts, 37, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath1Level : Symbol(staticPath1Level, Decl(spreadsAndContextualTupleTypes.ts, 17, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const d2 = foo([...staticPath2Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
>d2 : Symbol(d2, Decl(spreadsAndContextualTupleTypes.ts, 38, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath2Level : Symbol(staticPath2Level, Decl(spreadsAndContextualTupleTypes.ts, 18, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

const d3 = foo([...staticPath3Level, randomID, 'folder', 'subfolder', 'another-subfolder', 'doc.pdf']);
>d3 : Symbol(d3, Decl(spreadsAndContextualTupleTypes.ts, 39, 5))
>foo : Symbol(foo, Decl(spreadsAndContextualTupleTypes.ts, 21, 32))
>staticPath3Level : Symbol(staticPath3Level, Decl(spreadsAndContextualTupleTypes.ts, 19, 5))
>randomID : Symbol(randomID, Decl(spreadsAndContextualTupleTypes.ts, 21, 5))

Loading