Contextual typing for computed properties

This commit is contained in:
Jason Freeman
2015-01-14 12:09:46 -08:00
parent 4c5db716f9
commit 1b35a1be25
10 changed files with 211 additions and 12 deletions

View File

@@ -5165,13 +5165,26 @@ module ts {
function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElement) {
var objectLiteral = <ObjectLiteralExpression>element.parent;
var type = getContextualType(objectLiteral);
// TODO(jfreeman): Handle this case for computed names and symbols
var name = (<Identifier>element.name).text;
if (type && name) {
return getTypeOfPropertyOfContextualType(type, name) ||
isNumericName(name) && getIndexTypeOfContextualType(type, IndexKind.Number) ||
if (type) {
if (!hasComputedNameButNotSymbol(element)) {
// For a (non-symbol) computed property, there is no reason to look up the name
// in the type. It will just be "__computed", which does not appear in any
// SymbolTable.
var symbolName = getSymbolOfNode(element).name;
var propertyType = getTypeOfPropertyOfContextualType(type, symbolName);
if (propertyType) {
return propertyType;
}
}
var nameNode = element.name;
var propertyHasNumericName = nameNode.kind === SyntaxKind.ComputedPropertyName
? isNumericComputedName(<ComputedPropertyName>nameNode)
: isNumericName((<Identifier>nameNode).text);
return propertyHasNumericName && getIndexTypeOfContextualType(type, IndexKind.Number) ||
getIndexTypeOfContextualType(type, IndexKind.String);
}
return undefined;
}
@@ -5370,6 +5383,10 @@ module ts {
return createArrayType(getUnionType(elementTypes));
}
function isNumericComputedName(name: ComputedPropertyName): boolean {
return !!(checkExpression(name.expression).flags & TypeFlags.Number);
}
function isNumericName(name: string) {
// The intent of numeric names is that
// - they are names with text in a numeric form, and that
@@ -5395,15 +5412,20 @@ module ts {
return (+name).toString() === name;
}
function checkComputedPropertyName(node: ComputedPropertyName): void {
var computedNameType = checkExpression(node.expression);
function checkComputedPropertyName(node: ComputedPropertyName): Type {
var links = getNodeLinks(node.expression);
if (!links.resolvedType) {
links.resolvedType = checkExpression(node.expression);
// This will only allow types number, string, or any. Any types more complex will
// be disallowed, even union types like string | number. In the future, we might consider
// allowing types like that.
if ((computedNameType.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Any)) === 0) {
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_or_any);
// This will only allow types number, string, or any. Any types more complex will
// be disallowed, even union types like string | number. In the future, we might consider
// allowing types like that.
if ((links.resolvedType.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Any)) === 0) {
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_or_any);
}
}
return links.resolvedType;
}
function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper?: TypeMapper): Type {

View File

@@ -0,0 +1,18 @@
//// [computedPropertyNamesContextualType1.ts]
interface I {
[s: string]: (x: string) => number;
[s: number]: (x: any) => number; // Doesn't get hit
}
var o: I = {
["" + 0](y) { return y.length; },
["" + 1]: y => y.length
}
//// [computedPropertyNamesContextualType1.js]
var o = {
["" + 0](y) {
return y.length;
},
["" + 1]: function (y) { return y.length; }
};

View File

@@ -0,0 +1,33 @@
=== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType1.ts ===
interface I {
>I : I
[s: string]: (x: string) => number;
>s : string
>x : string
[s: number]: (x: any) => number; // Doesn't get hit
>s : number
>x : any
}
var o: I = {
>o : I
>I : I
>{ ["" + 0](y) { return y.length; }, ["" + 1]: y => y.length} : { [x: string]: undefined; [x: number]: undefined; }
["" + 0](y) { return y.length; },
>"" + 0 : string
>y : string
>y.length : number
>y : string
>length : number
["" + 1]: y => y.length
>"" + 1 : string
>y => y.length : (y: string) => number
>y : string
>y.length : number
>y : string
>length : number
}

View File

@@ -0,0 +1,18 @@
//// [computedPropertyNamesContextualType2.ts]
interface I {
[s: string]: (x: any) => number; // Doesn't get hit
[s: number]: (x: string) => number;
}
var o: I = {
[+"foo"](y) { return y.length; },
[+"bar"]: y => y.length
}
//// [computedPropertyNamesContextualType2.js]
var o = {
[+"foo"](y) {
return y.length;
},
[+"bar"]: function (y) { return y.length; }
};

View File

@@ -0,0 +1,33 @@
=== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType2.ts ===
interface I {
>I : I
[s: string]: (x: any) => number; // Doesn't get hit
>s : string
>x : any
[s: number]: (x: string) => number;
>s : number
>x : string
}
var o: I = {
>o : I
>I : I
>{ [+"foo"](y) { return y.length; }, [+"bar"]: y => y.length} : { [x: string]: undefined; [x: number]: undefined; }
[+"foo"](y) { return y.length; },
>+"foo" : number
>y : string
>y.length : number
>y : string
>length : number
[+"bar"]: y => y.length
>+"bar" : number
>y => y.length : (y: string) => number
>y : string
>y.length : number
>y : string
>length : number
}

View File

@@ -0,0 +1,17 @@
//// [computedPropertyNamesContextualType3.ts]
interface I {
[s: string]: (x: string) => number;
}
var o: I = {
[+"foo"](y) { return y.length; },
[+"bar"]: y => y.length
}
//// [computedPropertyNamesContextualType3.js]
var o = {
[+"foo"](y) {
return y.length;
},
[+"bar"]: function (y) { return y.length; }
};

View File

@@ -0,0 +1,29 @@
=== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType3.ts ===
interface I {
>I : I
[s: string]: (x: string) => number;
>s : string
>x : string
}
var o: I = {
>o : I
>I : I
>{ [+"foo"](y) { return y.length; }, [+"bar"]: y => y.length} : { [x: string]: undefined; }
[+"foo"](y) { return y.length; },
>+"foo" : number
>y : string
>y.length : number
>y : string
>length : number
[+"bar"]: y => y.length
>+"bar" : number
>y => y.length : (y: string) => number
>y : string
>y.length : number
>y : string
>length : number
}

View File

@@ -0,0 +1,10 @@
// @target: es6
interface I {
[s: string]: (x: string) => number;
[s: number]: (x: any) => number; // Doesn't get hit
}
var o: I = {
["" + 0](y) { return y.length; },
["" + 1]: y => y.length
}

View File

@@ -0,0 +1,10 @@
// @target: es6
interface I {
[s: string]: (x: any) => number; // Doesn't get hit
[s: number]: (x: string) => number;
}
var o: I = {
[+"foo"](y) { return y.length; },
[+"bar"]: y => y.length
}

View File

@@ -0,0 +1,9 @@
// @target: es6
interface I {
[s: string]: (x: string) => number;
}
var o: I = {
[+"foo"](y) { return y.length; },
[+"bar"]: y => y.length
}