Skip to content

Narrow contextual type of object literals #18488

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

Closed
wants to merge 4 commits into from
Closed
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
22 changes: 12 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5762,18 +5762,12 @@ namespace ts {
// Finally, iterate over the constituents of the resulting iteration type.
const keyType = constraintType.flags & TypeFlags.TypeVariable ? getApparentType(constraintType) : constraintType;
const iterationType = keyType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>keyType).type)) : keyType;
forEachType(iterationType, addMemberForKeyType);
// Note: The arrow function is needed to avoid passing forEach's second-arg (index) to the second-param (propertySymbol). The compiler doesn't catch this.
forEachType(iterationType, t => addMemberForKeyType(t));
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);

function addMemberForKeyType(t: Type, propertySymbolOrIndex?: Symbol | number) {
let propertySymbol: Symbol;
// forEachType delegates to forEach, which calls with a numeric second argument
// the type system currently doesn't catch this incompatibility, so we annotate
// the function ourselves to indicate the runtime behavior and deal with it here
if (typeof propertySymbolOrIndex === "object") {
propertySymbol = propertySymbolOrIndex;
}
function addMemberForKeyType(t: Type, propertySymbol?: Symbol) {
// Create a mapper from T to the current iteration type constituent. Then, if the
// mapped type is itself an instantiated type, combine the iteration mapper with the
// instantiation mapper.
Expand Down Expand Up @@ -18013,9 +18007,17 @@ namespace ts {
return false;
}

function isDiscriminantPropertyAssignment(node: Node) {
if (node && node.kind === SyntaxKind.PropertyAssignment) {
const assignment = node as PropertyAssignment;
return isIdentifier(assignment.name) && isDiscriminantProperty(getContextualType(node.parent as ObjectLiteralExpression), assignment.name.escapedText);
}
}

function checkExpressionForMutableLocation(node: Expression, checkMode?: CheckMode): Type {
const type = checkExpression(node, checkMode);
return isTypeAssertion(node) || isLiteralContextualType(getContextualType(node)) ? type : getWidenedLiteralType(type);
return isTypeAssertion(node) || isLiteralContextualType(getContextualType(node) ) || isDiscriminantPropertyAssignment(node.parent) ?
type : getWidenedLiteralType(type);
}

function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type {
Expand Down
26 changes: 26 additions & 0 deletions tests/baselines/reference/narrowContextualTypeOfObjectLiteral.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//// [narrowContextualTypeOfObjectLiteral.ts]
interface X {
type: 'x';
value: 1 | 2 | 3;
xtra: number;
}

interface Y {
type: 'y';
value: 11 | 12 | 13;
ytra: number;
}

let resa: X | Y = {
type: 'y',
value: 11,
ytra: 12
};


//// [narrowContextualTypeOfObjectLiteral.js]
var resa = {
type: 'y',
value: 11,
ytra: 12
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
=== tests/cases/compiler/narrowContextualTypeOfObjectLiteral.ts ===
interface X {
>X : Symbol(X, Decl(narrowContextualTypeOfObjectLiteral.ts, 0, 0))

type: 'x';
>type : Symbol(X.type, Decl(narrowContextualTypeOfObjectLiteral.ts, 0, 13))

value: 1 | 2 | 3;
>value : Symbol(X.value, Decl(narrowContextualTypeOfObjectLiteral.ts, 1, 14))

xtra: number;
>xtra : Symbol(X.xtra, Decl(narrowContextualTypeOfObjectLiteral.ts, 2, 21))
}

interface Y {
>Y : Symbol(Y, Decl(narrowContextualTypeOfObjectLiteral.ts, 4, 1))

type: 'y';
>type : Symbol(Y.type, Decl(narrowContextualTypeOfObjectLiteral.ts, 6, 13))

value: 11 | 12 | 13;
>value : Symbol(Y.value, Decl(narrowContextualTypeOfObjectLiteral.ts, 7, 14))

ytra: number;
>ytra : Symbol(Y.ytra, Decl(narrowContextualTypeOfObjectLiteral.ts, 8, 24))
}

let resa: X | Y = {
>resa : Symbol(resa, Decl(narrowContextualTypeOfObjectLiteral.ts, 12, 3))
>X : Symbol(X, Decl(narrowContextualTypeOfObjectLiteral.ts, 0, 0))
>Y : Symbol(Y, Decl(narrowContextualTypeOfObjectLiteral.ts, 4, 1))

type: 'y',
>type : Symbol(type, Decl(narrowContextualTypeOfObjectLiteral.ts, 12, 19))

value: 11,
>value : Symbol(value, Decl(narrowContextualTypeOfObjectLiteral.ts, 13, 14))

ytra: 12
>ytra : Symbol(ytra, Decl(narrowContextualTypeOfObjectLiteral.ts, 14, 14))

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
=== tests/cases/compiler/narrowContextualTypeOfObjectLiteral.ts ===
interface X {
>X : X

type: 'x';
>type : "x"

value: 1 | 2 | 3;
>value : 1 | 2 | 3

xtra: number;
>xtra : number
}

interface Y {
>Y : Y

type: 'y';
>type : "y"

value: 11 | 12 | 13;
>value : 11 | 12 | 13

ytra: number;
>ytra : number
}

let resa: X | Y = {
>resa : X | Y
>X : X
>Y : Y
>{ type: 'y', value: 11, ytra: 12} : { type: "y"; value: 11; ytra: number; }

type: 'y',
>type : string
>'y' : "y"

value: 11,
>value : number
>11 : 11

ytra: 12
>ytra : number
>12 : 12

};

17 changes: 17 additions & 0 deletions tests/cases/compiler/narrowContextualTypeOfObjectLiteral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface X {
type: 'x';
value: 1 | 2 | 3;
xtra: number;
}

interface Y {
type: 'y';
value: 11 | 12 | 13;
ytra: number;
}

let resa: X | Y = {
type: 'y',
value: 11,
ytra: 12
};