Skip to content

Commit fd5c401

Browse files
committedNov 5, 2014
Close a hole in type guards of property access with string literals. Add basic but sound switch statement guards over the theses same property access.
1 parent d82f8f5 commit fd5c401

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed
 

‎src/compiler/checker.ts

+60-12
Original file line numberDiff line numberDiff line change
@@ -4196,13 +4196,20 @@ module ts {
41964196
return type;
41974197
}
41984198

4199-
function keepAssignableTypes(type : Type, targetType : Type, assumeAssignable: boolean): Type {
4199+
function keepAssignablePropertyTypes(type : Type, propertyName : string, targetPropertyType : Type, assumeAssignable: boolean): Type {
42004200
if(type.flags & TypeFlags.Union) {
42014201
var types = (<UnionType>type).types;
42024202
} else {
42034203
var types = [type];
42044204
}
4205-
var remainingTypes = filter(types, t => assumeAssignable ? isTypeAssignableTo(t, targetType) : !isTypeAssignableTo(t, targetType));
4205+
var remainingTypes = filter(types, t => {
4206+
var propertyType = getTypeOfPropertyOfContextualType(t, propertyName);
4207+
if(propertyType) {
4208+
return assumeAssignable ? isTypeAssignableTo(targetPropertyType, propertyType) : !isTypeAssignableTo(propertyType, targetPropertyType);
4209+
} else {
4210+
return !assumeAssignable;
4211+
}
4212+
});
42064213
if(remainingTypes.length > 0) {
42074214
return getUnionType(remainingTypes);
42084215
}
@@ -4322,6 +4329,11 @@ module ts {
43224329
}
43234330
}
43244331
break;
4332+
case SyntaxKind.SwitchStatement:
4333+
if (child !== (<SwitchStatement>node).expression) {
4334+
narrowedType = narrowTypeInCaseClause(type, <SwitchStatement>node, <CaseOrDefaultClause>child);
4335+
}
4336+
break;
43254337
}
43264338
// Only use narrowed type if construct contains no assignments to variable
43274339
if (narrowedType !== type) {
@@ -4360,9 +4372,7 @@ module ts {
43604372
}
43614373
}
43624374

4363-
function narrowPropTypeByStringTypeEquality(type : Type, expr: BinaryExpression, assumeTrue: boolean): Type {
4364-
var left = <PropertyAccess>expr.left;
4365-
var right = expr.right;
4375+
function narrowPropTypeByStringTypeEquality(type : Type, left : PropertyAccess, right : Expression, assumeTrue: boolean): Type {
43664376
var right_t = checkExpression(right);
43674377
if (left.kind !== SyntaxKind.PropertyAccess || left.left.kind !== SyntaxKind.Identifier ||
43684378
!(right_t.flags & TypeFlags.StringLiteral) ||
@@ -4374,12 +4384,49 @@ module ts {
43744384
if (isTypeAssignableTo(right_t, t)) {
43754385
smallerType = right_t;
43764386
}
4377-
var dummyProperties: SymbolTable = {};
4378-
var dummyProperty = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient, left.right.text);
4379-
dummyProperty.type = smallerType;
4380-
dummyProperties[dummyProperty.name] = dummyProperty;
4381-
var dummyType = createAnonymousType(undefined, dummyProperties, emptyArray, emptyArray, undefined, undefined);
4382-
return keepAssignableTypes(type, dummyType, assumeTrue);
4387+
var propertyName = left.right.text;
4388+
return keepAssignablePropertyTypes(type, propertyName, smallerType, assumeTrue);
4389+
}
4390+
4391+
function narrowTypeInCaseClause(type : Type, switchNode : SwitchStatement, caseClause : CaseOrDefaultClause) : Type {
4392+
var propertyAccess = <PropertyAccess>switchNode.expression;
4393+
if(switchNode.expression.kind !== SyntaxKind.PropertyAccess ||
4394+
getResolvedSymbol(<Identifier>propertyAccess.left) !== symbol) {
4395+
console.log("return from alien switch for " + symbol.name);
4396+
return type;
4397+
}
4398+
var narrowedType = type;
4399+
var remainingType = type;
4400+
var typesBeforeBreak : Type[] = [];
4401+
for (var i = 0; i < switchNode.clauses.length; i++) {
4402+
var clause = switchNode.clauses[i];
4403+
if (clause.expression) {
4404+
narrowedType = narrowPropTypeByStringTypeEquality(remainingType, <PropertyAccess>switchNode.expression, clause.expression, /* assumeTrue */ true);
4405+
typesBeforeBreak.push(narrowedType);
4406+
narrowedType = getUnionType(typesBeforeBreak);
4407+
remainingType = narrowPropTypeByStringTypeEquality(remainingType, <PropertyAccess>switchNode.expression, clause.expression, /* assumeTrue */ false);
4408+
4409+
} else {
4410+
narrowedType = remainingType;
4411+
}
4412+
console.log("clause id : " + clause.id + " while waiting for " + caseClause.id);
4413+
if (clause.id === caseClause.id) {
4414+
console.log("returning in clause : " + typeToString(narrowedType));
4415+
return narrowedType;
4416+
}
4417+
if(clause.statements && clause.statements.length > 0) {
4418+
var statements = clause.statements;
4419+
var last = statements[statements.length - 1];
4420+
if (last.kind === SyntaxKind.ReturnStatement ||
4421+
last.kind === SyntaxKind.BreakStatement) {
4422+
typesBeforeBreak = [];
4423+
}
4424+
}
4425+
}
4426+
4427+
console.log("attained default clause");
4428+
4429+
return narrowedType;
43834430
}
43844431

43854432
function narrowTypeByAnd(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
@@ -4440,7 +4487,8 @@ module ts {
44404487
var operator = (<BinaryExpression>expr).operator;
44414488
if (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
44424489
if((<BinaryExpression>expr).left.kind === SyntaxKind.PropertyAccess) {
4443-
return narrowPropTypeByStringTypeEquality(type, <BinaryExpression>expr, assumeTrue);
4490+
var binary_expr = <BinaryExpression>expr;
4491+
return narrowPropTypeByStringTypeEquality(type, <PropertyAccess>binary_expr.left, binary_expr.right, assumeTrue);
44444492
} else {
44454493
return narrowTypeByEquality(type, <BinaryExpression>expr, assumeTrue);
44464494
}

‎src/compiler/parser.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1240,15 +1240,16 @@ module ts {
12401240
}
12411241

12421242
// Parses a list of elements
1243-
function parseList<T extends Node>(kind: ParsingContext, checkForStrictMode: boolean, parseElement: () => T): NodeArray<T> {
1243+
function parseList<T extends Node>(kind: ParsingContext, checkForStrictMode: boolean, parseElement: (i:number) => T): NodeArray<T> {
12441244
var saveParsingContext = parsingContext;
12451245
parsingContext |= 1 << kind;
12461246
var result = <NodeArray<T>>[];
12471247
result.pos = getNodePos();
12481248
var saveIsInStrictMode = isInStrictMode;
1249+
var i = 0;
12491250
while (!isListTerminator(kind)) {
12501251
if (isListElement(kind, /* inErrorRecovery */ false)) {
1251-
var element = parseElement();
1252+
var element = parseElement(i++);
12521253
result.push(element);
12531254
// test elements only if we are not already in strict mode
12541255
if (!isInStrictMode && checkForStrictMode) {
@@ -2993,25 +2994,27 @@ module ts {
29932994
return node;
29942995
}
29952996

2996-
function parseCaseClause(): CaseOrDefaultClause {
2997+
function parseCaseClause(i: number): CaseOrDefaultClause {
29972998
var node = <CaseOrDefaultClause>createNode(SyntaxKind.CaseClause);
29982999
parseExpected(SyntaxKind.CaseKeyword);
29993000
node.expression = parseExpression();
30003001
parseExpected(SyntaxKind.ColonToken);
30013002
node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatementAllowingLetDeclaration);
3003+
node.id = i;
30023004
return finishNode(node);
30033005
}
30043006

3005-
function parseDefaultClause(): CaseOrDefaultClause {
3007+
function parseDefaultClause(i: number): CaseOrDefaultClause {
30063008
var node = <CaseOrDefaultClause>createNode(SyntaxKind.DefaultClause);
30073009
parseExpected(SyntaxKind.DefaultKeyword);
30083010
parseExpected(SyntaxKind.ColonToken);
30093011
node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatementAllowingLetDeclaration);
3012+
node.id = i;
30103013
return finishNode(node);
30113014
}
30123015

3013-
function parseCaseOrDefaultClause(): CaseOrDefaultClause {
3014-
return token === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause();
3016+
function parseCaseOrDefaultClause(i: number): CaseOrDefaultClause {
3017+
return token === SyntaxKind.CaseKeyword ? parseCaseClause(i) : parseDefaultClause(i);
30153018
}
30163019

30173020
function parseSwitchStatement(): SwitchStatement {

‎src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ module ts {
488488
export interface CaseOrDefaultClause extends Node {
489489
expression?: Expression;
490490
statements: NodeArray<Statement>;
491+
id: number;
491492
}
492493

493494
export interface LabeledStatement extends Statement {

0 commit comments

Comments
 (0)