diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b198b827d0c..2f236ed6e44 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3987,6 +3987,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); @@ -5951,6 +5964,23 @@ namespace ts { return result; } + function eachPropertyRelatedTo(source: Type, target: Type, numericPropertiesOnly: boolean, reportErrors: boolean): Ternary { + let result = Ternary.True; + for (const prop of getPropertiesOfObjectType(source)) { + if (!numericPropertiesOnly || 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; + } + } + return result; + } + function stringIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.String, source, target); @@ -5964,6 +5994,9 @@ namespace ts { } const sourceInfo = getIndexInfoOfType(source, IndexKind.String); if (!sourceInfo) { + if (isObjectLiteralType(source)) { + return eachPropertyRelatedTo(source, targetInfo.type, /* numericPropertiesOnly*/ false, reportErrors); + } if (reportErrors) { reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } @@ -5995,6 +6028,9 @@ namespace ts { const sourceStringInfo = getIndexInfoOfType(source, IndexKind.String); const sourceNumberInfo = getIndexInfoOfType(source, IndexKind.Number); if (!(sourceStringInfo || sourceNumberInfo)) { + if (isObjectLiteralType(source)) { + return eachPropertyRelatedTo(source, targetInfo.type, /* numericPropertiesOnly*/ true, reportErrors); + } if (reportErrors) { reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } @@ -6263,6 +6299,15 @@ namespace ts { return !!(type.flags & TypeFlags.Tuple); } + /** + * Return true if type was inferred from an object literal or written as an object type literal + */ + 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 = (type).regularType; @@ -6610,9 +6655,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--; } } @@ -6644,12 +6687,22 @@ namespace ts { inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } - 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); } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 5b93f61bb90..2458e68f23c 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1699,6 +1699,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