mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-22 02:35:48 -05:00
Merge pull request #7029 from Microsoft/implicitIndexSignatures
Implicit index signatures
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user