Merge pull request #7029 from Microsoft/implicitIndexSignatures

Implicit index signatures
This commit is contained in:
Anders Hejlsberg
2016-02-16 15:37:18 -08:00
129 changed files with 1053 additions and 507 deletions

View File

@@ -3996,6 +3996,19 @@ namespace ts {
return getIndexTypeOfStructuredType(getApparentType(type), kind);
}
function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type {
if (isObjectLiteralType(type)) {
const propTypes: Type[] = [];
for (const prop of getPropertiesOfType(type)) {
if (kind === IndexKind.String || isNumericLiteralName(prop.name)) {
propTypes.push(getTypeOfSymbol(prop));
}
}
return getUnionType(propTypes);
}
return undefined;
}
function getTypeParametersFromJSDocTemplate(declaration: SignatureDeclaration): TypeParameter[] {
if (declaration.flags & NodeFlags.JavaScriptFile) {
const templateTag = getJSDocTemplateTag(declaration);
@@ -5752,9 +5765,9 @@ namespace ts {
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors);
if (result) {
result &= stringIndexTypesRelatedTo(source, originalSource, target, reportErrors);
result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.String, reportErrors);
if (result) {
result &= numberIndexTypesRelatedTo(source, originalSource, target, reportErrors);
result &= indexTypesRelatedTo(source, originalSource, target, IndexKind.Number, reportErrors);
}
}
}
@@ -5956,76 +5969,65 @@ namespace ts {
return result;
}
function stringIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return indexTypesIdenticalTo(IndexKind.String, source, target);
function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary {
let result = Ternary.True;
for (const prop of getPropertiesOfObjectType(source)) {
if (kind === IndexKind.String || isNumericLiteralName(prop.name)) {
const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop));
}
return Ternary.False;
}
result &= related;
}
}
const targetInfo = getIndexInfoOfType(target, IndexKind.String);
if (targetInfo) {
if ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) {
// non-primitive assignment to any is always allowed, eg
// `var x: { [index: string]: any } = { property: 12 };`
return Ternary.True;
}
const sourceInfo = getIndexInfoOfType(source, IndexKind.String);
if (!sourceInfo) {
if (reportErrors) {
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
return result;
}
function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) {
const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors);
if (!related && reportErrors) {
reportError(Diagnostics.Index_signatures_are_incompatible);
}
return related;
}
function indexTypesRelatedTo(source: Type, originalSource: Type, target: Type, kind: IndexKind, reportErrors: boolean) {
if (relation === identityRelation) {
return indexTypesIdenticalTo(source, target, kind);
}
const targetInfo = getIndexInfoOfType(target, kind);
if (!targetInfo || ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive))) {
// Index signature of type any permits assignment from everything but primitives
return Ternary.True;
}
const sourceInfo = getIndexInfoOfType(source, kind) ||
kind === IndexKind.Number && getIndexInfoOfType(source, IndexKind.String);
if (sourceInfo) {
return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors);
}
if (isObjectLiteralType(source)) {
let related = Ternary.True;
if (kind === IndexKind.String) {
const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number);
if (sourceNumberInfo) {
related = indexInfoRelatedTo(sourceNumberInfo, targetInfo, reportErrors);
}
return Ternary.False;
}
const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Index_signatures_are_incompatible);
}
return Ternary.False;
if (related) {
related &= eachPropertyRelatedTo(source, targetInfo.type, kind, reportErrors);
}
return related;
}
return Ternary.True;
if (reportErrors) {
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
}
return Ternary.False;
}
function numberIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return indexTypesIdenticalTo(IndexKind.Number, source, target);
}
const targetInfo = getIndexInfoOfType(target, IndexKind.Number);
if (targetInfo) {
if ((targetInfo.type.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) {
// non-primitive assignment to any is always allowed, eg
// `var x: { [index: number]: any } = { property: 12 };`
return Ternary.True;
}
const sourceStringInfo = getIndexInfoOfType(source, IndexKind.String);
const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number);
if (!(sourceStringInfo || sourceNumberInfo)) {
if (reportErrors) {
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
}
return Ternary.False;
}
let related: Ternary;
if (sourceStringInfo && sourceNumberInfo) {
// If we know for sure we're testing both string and numeric index types then only report errors from the second one
related = isRelatedTo(sourceStringInfo.type, targetInfo.type, /*reportErrors*/ false) ||
isRelatedTo(sourceNumberInfo.type, targetInfo.type, reportErrors);
}
else {
related = isRelatedTo((sourceStringInfo || sourceNumberInfo).type, targetInfo.type, reportErrors);
}
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Index_signatures_are_incompatible);
}
return Ternary.False;
}
return related;
}
return Ternary.True;
}
function indexTypesIdenticalTo(indexKind: IndexKind, source: Type, target: Type): Ternary {
function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary {
const targetInfo = getIndexInfoOfType(target, indexKind);
const sourceInfo = getIndexInfoOfType(source, indexKind);
if (!sourceInfo && !targetInfo) {
@@ -6268,6 +6270,16 @@ namespace ts {
return !!(type.flags & TypeFlags.Tuple);
}
/**
* Return true if type was inferred from an object literal or written as an object type literal
* with no call or construct signatures.
*/
function isObjectLiteralType(type: Type) {
return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral)) !== 0 &&
getSignaturesOfType(type, SignatureKind.Call).length === 0 &&
getSignaturesOfType(type, SignatureKind.Construct).length === 0;
}
function getRegularTypeOfObjectLiteral(type: Type): Type {
if (type.flags & TypeFlags.FreshObjectLiteral) {
let regularType = (<FreshObjectLiteralType>type).regularType;
@@ -6607,9 +6619,7 @@ namespace ts {
inferFromProperties(source, target);
inferFromSignatures(source, target, SignatureKind.Call);
inferFromSignatures(source, target, SignatureKind.Construct);
inferFromIndexTypes(source, target, IndexKind.String, IndexKind.String);
inferFromIndexTypes(source, target, IndexKind.Number, IndexKind.Number);
inferFromIndexTypes(source, target, IndexKind.String, IndexKind.Number);
inferFromIndexTypes(source, target);
depth--;
}
}
@@ -6647,12 +6657,22 @@ namespace ts {
}
}
function inferFromIndexTypes(source: Type, target: Type, sourceKind: IndexKind, targetKind: IndexKind) {
const targetIndexType = getIndexTypeOfType(target, targetKind);
if (targetIndexType) {
const sourceIndexType = getIndexTypeOfType(source, sourceKind);
function inferFromIndexTypes(source: Type, target: Type) {
const targetStringIndexType = getIndexTypeOfType(target, IndexKind.String);
if (targetStringIndexType) {
const sourceIndexType = getIndexTypeOfType(source, IndexKind.String) ||
getImplicitIndexTypeOfType(source, IndexKind.String);
if (sourceIndexType) {
inferFromTypes(sourceIndexType, targetIndexType);
inferFromTypes(sourceIndexType, targetStringIndexType);
}
}
const targetNumberIndexType = getIndexTypeOfType(target, IndexKind.Number);
if (targetNumberIndexType) {
const sourceIndexType = getIndexTypeOfType(source, IndexKind.Number) ||
getIndexTypeOfType(source, IndexKind.String) ||
getImplicitIndexTypeOfType(source, IndexKind.Number);
if (sourceIndexType) {
inferFromTypes(sourceIndexType, targetNumberIndexType);
}
}
}
@@ -7865,11 +7885,6 @@ namespace ts {
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isTupleLikeType) : isTupleLikeType(type));
}
// Return true if the given contextual type provides an index signature of the given kind
function contextualTypeHasIndexSignature(type: Type, kind: IndexKind): boolean {
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, t => getIndexInfoOfStructuredType(t, kind)) : getIndexInfoOfStructuredType(type, kind));
}
// In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of
// the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one
// exists. Otherwise, it is the type of the string index signature in T, if one exists.
@@ -8265,6 +8280,17 @@ namespace ts {
return links.resolvedType;
}
function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, properties: Symbol[], kind: IndexKind): IndexInfo {
const propTypes: Type[] = [];
for (let i = 0; i < properties.length; i++) {
if (kind === IndexKind.String || isNumericName(node.properties[i].name)) {
propTypes.push(getTypeOfSymbol(properties[i]));
}
}
const unionType = propTypes.length ? getUnionType(propTypes) : undefinedType;
return createIndexInfo(unionType, /*isReadonly*/ false);
}
function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper?: TypeMapper): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
@@ -8276,8 +8302,10 @@ namespace ts {
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
let typeFlags: TypeFlags = 0;
let patternWithComputedProperties = false;
let hasComputedStringProperty = false;
let hasComputedNumberProperty = false;
for (const memberDecl of node.properties) {
let member = memberDecl.symbol;
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
@@ -8341,7 +8369,15 @@ namespace ts {
checkAccessorDeclaration(<AccessorDeclaration>memberDecl);
}
if (!hasDynamicName(memberDecl)) {
if (hasDynamicName(memberDecl)) {
if (isNumericName(memberDecl.name)) {
hasComputedNumberProperty = true;
}
else {
hasComputedStringProperty = true;
}
}
else {
propertiesTable[member.name] = member;
}
propertiesArray.push(member);
@@ -8362,8 +8398,8 @@ namespace ts {
}
}
const stringIndexInfo = getIndexInfo(IndexKind.String);
const numberIndexInfo = getIndexInfo(IndexKind.Number);
const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.String) : undefined;
const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.Number) : undefined;
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral;
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags) | (patternWithComputedProperties ? TypeFlags.ObjectLiteralPatternWithComputedProperties : 0);
@@ -8371,29 +8407,6 @@ namespace ts {
result.pattern = node;
}
return result;
function getIndexInfo(kind: IndexKind) {
if (contextualType && contextualTypeHasIndexSignature(contextualType, kind)) {
const propTypes: Type[] = [];
for (let i = 0; i < propertiesArray.length; i++) {
const propertyDecl = node.properties[i];
if (kind === IndexKind.String || isNumericName(propertyDecl.name)) {
// Do not call getSymbolOfNode(propertyDecl), as that will get the
// original symbol for the node. We actually want to get the symbol
// created by checkObjectLiteral, since that will be appropriately
// contextually typed and resolved.
const type = getTypeOfSymbol(propertiesArray[i]);
if (!contains(propTypes, type)) {
propTypes.push(type);
}
}
}
const unionType = propTypes.length ? getUnionType(propTypes) : undefinedType;
typeFlags |= unionType.flags;
return createIndexInfo(unionType, /*isReadonly*/ false);
}
return undefined;
}
}
function checkJsxSelfClosingElement(node: JsxSelfClosingElement) {

View File

@@ -1703,6 +1703,10 @@
"category": "Error",
"code": 2529
},
"Property '{0}' is incompatible with index signature.": {
"category": "Error",
"code": 2530
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600