Improved type relation caching to fix #1002

This commit is contained in:
Anders Hejlsberg
2014-10-31 10:18:15 -07:00
parent e1dfdb4d25
commit 760a2968e9
2 changed files with 277 additions and 203 deletions

View File

@@ -2163,7 +2163,7 @@ module ts {
return false;
}
for (var i = 0; i < s.length; i++) {
if (!compareSignatures(s[i], t[i], /*compareReturnTypes*/ false, isTypeIdenticalTo)) {
if (!compareSignatures(s[i], t[i], /*compareReturnTypes*/ false, compareTypes)) {
return false;
}
}
@@ -3209,38 +3209,32 @@ module ts {
// TYPE CHECKING
var subtypeRelation: Map<boolean> = {};
var assignableRelation: Map<boolean> = {};
var identityRelation: Map<boolean> = {};
var subtypeRelation: Map<Ternary> = {};
var assignableRelation: Map<Ternary> = {};
var identityRelation: Map<Ternary> = {};
function isTypeIdenticalTo(source: Type, target: Type): boolean {
return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined);
}
function compareTypes(source: Type, target: Type): number {
return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined) ? -1 : 0;
}
function isTypeSubtypeOf(source: Type, target: Type): boolean {
return checkTypeSubtypeOf(source, target, /*errorNode*/ undefined);
}
function checkTypeSubtypeOf(
source: Type,
target: Type,
errorNode: Node,
headMessage?: DiagnosticMessage,
containingMessageChain?: DiagnosticMessageChain): boolean {
return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain);
}
function isTypeAssignableTo(source: Type, target: Type): boolean {
return checkTypeAssignableTo(source, target, /*errorNode*/ undefined);
}
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage);
function checkTypeSubtypeOf(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean {
return checkTypeRelatedTo(source, target, subtypeRelation, errorNode, headMessage, containingMessageChain);
}
function isTypeRelatedTo(source: Type, target: Type, relation: Map<boolean>): boolean {
return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined);
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage);
}
function isSignatureAssignableTo(source: Signature, target: Signature): boolean {
@@ -3249,71 +3243,10 @@ module ts {
return checkTypeRelatedTo(sourceType, targetType, assignableRelation, /*errorNode*/ undefined);
}
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
return isPropertyIdenticalToRecursive(sourceProp, targetProp, /*reportErrors*/ false, (s, t, _reportErrors) => isTypeIdenticalTo(s, t));
}
function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean {
if (!type.baseTypes.length || type.baseTypes.length === 1) {
return true;
}
var seen: Map<{ prop: Symbol; containingType: Type }> = {};
forEach(type.declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; });
var ok = true;
for (var i = 0, len = type.baseTypes.length; i < len; ++i) {
var base = type.baseTypes[i];
var properties = getPropertiesOfObjectType(base);
for (var j = 0, proplen = properties.length; j < proplen; ++j) {
var prop = properties[j];
if (!hasProperty(seen, prop.name)) {
seen[prop.name] = { prop: prop, containingType: base };
}
else {
var existing = seen[prop.name];
var isInheritedProperty = existing.containingType !== type;
if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) {
ok = false;
var typeName1 = typeToString(existing.containingType);
var typeName2 = typeToString(base);
var errorInfo = chainDiagnosticMessages(undefined, Diagnostics.Named_properties_0_of_types_1_and_2_are_not_identical, prop.name, typeName1, typeName2);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2);
addDiagnostic(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo, program.getCompilerHost().getNewLine()));
}
}
}
}
return ok;
}
function isPropertyIdenticalToRecursive(sourceProp: Symbol, targetProp: Symbol, reportErrors: boolean, relate: (source: Type, target: Type, reportErrors: boolean) => boolean): boolean {
// Two members are considered identical when
// - they are public properties with identical names, optionality, and types,
// - they are private or protected properties originating in the same declaration and having identical types
if (sourceProp === targetProp) {
return true;
}
var sourcePropAccessibility = getDeclarationFlagsFromSymbol(sourceProp) & (NodeFlags.Private | NodeFlags.Protected);
var targetPropAccessibility = getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected);
if (sourcePropAccessibility !== targetPropAccessibility) {
return false;
}
if (sourcePropAccessibility) {
return getTargetSymbol(sourceProp) === getTargetSymbol(targetProp) && relate(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors);
}
else {
return isOptionalProperty(sourceProp) === isOptionalProperty(targetProp) && relate(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors);
}
}
function checkTypeRelatedTo(
source: Type,
target: Type,
relation: Map<boolean>,
relation: Map<Ternary>,
errorNode: Node,
headMessage?: DiagnosticMessage,
containingMessageChain?: DiagnosticMessageChain): boolean {
@@ -3327,7 +3260,7 @@ module ts {
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking");
var result = isRelatedToWithCustomErrors(source, target, errorNode !== undefined, headMessage);
var result = isRelatedTo(source, target, errorNode !== undefined, headMessage);
if (overflow) {
error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
}
@@ -3337,54 +3270,55 @@ module ts {
}
addDiagnostic(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo, program.getCompilerHost().getNewLine()));
}
return result;
return result !== 0;
function reportError(message: DiagnosticMessage, arg0?: string, arg1?: string, arg2?: string): void {
errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2);
}
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean): boolean {
return isRelatedToWithCustomErrors(source, target, reportErrors, /*headMessage*/ undefined);
}
function isRelatedToWithCustomErrors(source: Type, target: Type, reportErrors: boolean, headMessage: DiagnosticMessage): boolean {
// Compare two types and return
// Ternary.True if they are related with no assumptions,
// Ternary.Maybe if they are related with assumptions of other relationships, or
// Ternary.False if they are not related.
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage): Ternary {
var result: Ternary;
if (relation === identityRelation) {
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
if (source === target) return true;
if (source === target) return Ternary.True;
}
else {
if (source === target) return true;
if (target.flags & TypeFlags.Any) return true;
if (source === undefinedType) return true;
if (source === nullType && target !== undefinedType) return true;
if (source.flags & TypeFlags.Enum && target === numberType) return true;
if (source.flags & TypeFlags.StringLiteral && target === stringType) return true;
if (source === target) return Ternary.True;
if (target.flags & TypeFlags.Any) return Ternary.True;
if (source === undefinedType) return Ternary.True;
if (source === nullType && target !== undefinedType) return Ternary.True;
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
if (relation === assignableRelation) {
if (source.flags & TypeFlags.Any) return true;
if (source === numberType && target.flags & TypeFlags.Enum) return true;
if (source.flags & TypeFlags.Any) return Ternary.True;
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
}
}
if (source.flags & TypeFlags.Union) {
if (unionTypeRelatedToType(<UnionType>source, target, reportErrors)) {
return true;
if (result = unionTypeRelatedToType(<UnionType>source, target, reportErrors)) {
return result;
}
}
else if (target.flags & TypeFlags.Union) {
if (typeRelatedToUnionType(source, <UnionType>target, reportErrors)) {
return true;
if (result = typeRelatedToUnionType(source, <UnionType>target, reportErrors)) {
return result;
}
}
else if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) {
if (typeParameterRelatedTo(<TypeParameter>source, <TypeParameter>target, reportErrors)) {
return true;
if (result = typeParameterRelatedTo(<TypeParameter>source, <TypeParameter>target, reportErrors)) {
return result;
}
}
else {
var saveErrorInfo = errorInfo;
if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if relationship holds for all type arguments
if (typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
return true;
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
return result;
}
}
// Even if relationship doesn't hold for type arguments, it may hold in a structural comparison
@@ -3393,9 +3327,9 @@ module ts {
// identity relation does not use apparent type
var sourceOrApparentType = relation === identityRelation ? source : getApparentType(source);
if (sourceOrApparentType.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType &&
objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors)) {
(result = objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors))) {
errorInfo = saveErrorInfo;
return true;
return result;
}
}
if (reportErrors) {
@@ -3403,61 +3337,67 @@ module ts {
Debug.assert(headMessage);
reportError(headMessage, typeToString(source), typeToString(target));
}
return false;
return Ternary.False;
}
function typeRelatedToUnionType(source: Type, target: UnionType, reportErrors: boolean): boolean {
function typeRelatedToUnionType(source: Type, target: UnionType, reportErrors: boolean): Ternary {
var targetTypes = target.types;
for (var i = 0, len = targetTypes.length; i < len; i++) {
if (isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1)) {
return true;
var related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1);
if (related) {
return related;
}
}
return false;
return Ternary.False;
}
function unionTypeRelatedToType(source: UnionType, target: Type, reportErrors: boolean): boolean {
function unionTypeRelatedToType(source: UnionType, target: Type, reportErrors: boolean): Ternary {
var result = Ternary.True;
var sourceTypes = source.types;
for (var i = 0, len = sourceTypes.length; i < len; i++) {
if (!isRelatedTo(sourceTypes[i], target, reportErrors)) {
return false;
var related = isRelatedTo(sourceTypes[i], target, reportErrors);
if (!related) {
return Ternary.False;
}
result &= related;
}
return true;
return result;
}
function typesRelatedTo(sources: Type[], targets: Type[], reportErrors: boolean): boolean {
function typesRelatedTo(sources: Type[], targets: Type[], reportErrors: boolean): Ternary {
var result = Ternary.True;
for (var i = 0, len = sources.length; i < len; i++) {
if (!isRelatedTo(sources[i], targets[i], reportErrors)) return false;
var related = isRelatedTo(sources[i], targets[i], reportErrors);
if (!related) {
return Ternary.False;
}
result &= related;
}
return true;
return result;
}
function typeParameterRelatedTo(source: TypeParameter, target: TypeParameter, reportErrors: boolean): boolean {
function typeParameterRelatedTo(source: TypeParameter, target: TypeParameter, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
if (source.symbol.name !== target.symbol.name) {
return false;
return Ternary.False;
}
// covers case when both type parameters does not have constraint (both equal to noConstraintType)
if (source.constraint === target.constraint) {
return true;
return Ternary.True;
}
if (source.constraint === noConstraintType || target.constraint === noConstraintType) {
return false;
return Ternary.False;
}
return isRelatedTo(source.constraint, target.constraint, reportErrors);
}
else {
while (true) {
var constraint = getConstraintOfTypeParameter(source);
if (constraint === target) return true;
if (constraint === target) return Ternary.True;
if (!(constraint && constraint.flags & TypeFlags.TypeParameter)) break;
source = <TypeParameter>constraint;
}
return false;
return Ternary.False;
}
}
@@ -3466,18 +3406,23 @@ module ts {
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
// equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion
// and issue an error. Otherwise, actually compare the structure of the two types.
function objectTypeRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
if (overflow) return false;
var result: boolean;
function objectTypeRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): Ternary {
if (overflow) {
return Ternary.False;
}
var id = source.id + "," + target.id;
if ((result = relation[id]) !== undefined) return result;
var related = relation[id];
if (related !== undefined) {
return related;
}
if (depth > 0) {
for (var i = 0; i < depth; i++) {
if (source === sourceStack[i] && target === targetStack[i]) return true;
// If source and target are already being compared, consider them related with assumptions
if (source === sourceStack[i] && target === targetStack[i]) return Ternary.Maybe;
}
if (depth === 100) {
overflow = true;
return false;
return 0;
}
}
else {
@@ -3491,15 +3436,28 @@ module ts {
var saveExpandingFlags = expandingFlags;
if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack)) expandingFlags |= 1;
if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack)) expandingFlags |= 2;
result = expandingFlags === 3 ||
propertiesRelatedTo(source, target, reportErrors) &&
signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors) &&
signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors) &&
stringIndexTypesRelatedTo(source, target, reportErrors) &&
numberIndexTypesRelatedTo(source, target, reportErrors);
if (expandingFlags === 3) {
var result = Ternary.True;
}
else {
var result = propertiesRelatedTo(source, target, reportErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors);
if (result) {
result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors);
if (result) {
result &= stringIndexTypesRelatedTo(source, target, reportErrors);
if (result) {
result &= numberIndexTypesRelatedTo(source, target, reportErrors);
}
}
}
}
}
expandingFlags = saveExpandingFlags;
depth--;
if (depth === 0) {
// Only cache results that are free of assumptions
if (result !== Ternary.Maybe) {
relation[id] = result;
}
return result;
@@ -3525,10 +3483,11 @@ module ts {
return false;
}
function propertiesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
function propertiesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return propertiesIdenticalTo(source, target, reportErrors);
return propertiesIdenticalTo(source, target);
}
var result = Ternary.True;
var properties = getPropertiesOfObjectType(target);
for (var i = 0; i < properties.length; i++) {
var targetProp = properties[i];
@@ -3539,7 +3498,7 @@ module ts {
if (reportErrors) {
reportError(Diagnostics.Property_0_is_missing_in_type_1, symbolToString(targetProp), typeToString(source));
}
return false;
return Ternary.False;
}
}
else if (!(targetProp.flags & SymbolFlags.Prototype)) {
@@ -3557,7 +3516,7 @@ module ts {
typeToString(sourceFlags & NodeFlags.Private ? target : source));
}
}
return false;
return Ternary.False;
}
}
else if (targetFlags & NodeFlags.Protected) {
@@ -3569,7 +3528,7 @@ module ts {
reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2,
symbolToString(targetProp), typeToString(sourceClass || source), typeToString(targetClass));
}
return false;
return Ternary.False;
}
}
else if (sourceFlags & NodeFlags.Protected) {
@@ -3577,14 +3536,16 @@ module ts {
reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2,
symbolToString(targetProp), typeToString(source), typeToString(target));
}
return false;
return Ternary.False;
}
if (!isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors)) {
var related = isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
}
return false;
return Ternary.False;
}
result &= related;
if (isOptionalProperty(sourceProp) && !isOptionalProperty(targetProp)) {
// TypeScript 1.0 spec (April 2014): 3.8.3
// S is a subtype of a type T, and T is a supertype of S if ...
@@ -3597,37 +3558,46 @@ module ts {
reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2,
symbolToString(targetProp), typeToString(source), typeToString(target));
}
return false;
return Ternary.False;
}
}
}
}
return true;
return result;
}
function propertiesIdenticalTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
function propertiesIdenticalTo(source: ObjectType, target: ObjectType): Ternary {
var sourceProperties = getPropertiesOfObjectType(source);
var targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
return Ternary.False;
}
var result = Ternary.True;
for (var i = 0, len = sourceProperties.length; i < len; ++i) {
var sourceProp = sourceProperties[i];
var targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp || !isPropertyIdenticalToRecursive(sourceProp, targetProp, reportErrors, isRelatedTo)) {
return false;
if (!targetProp) {
return Ternary.False;
}
var related = compareProperties(sourceProp, targetProp, isRelatedTo);
if (!related) {
return Ternary.False;
}
result &= related;
}
return true;
return result;
}
function signaturesRelatedTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean {
function signaturesRelatedTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return signaturesIdenticalTo(source, target, kind, reportErrors);
return signaturesIdenticalTo(source, target, kind);
}
if (target === anyFunctionType || source === anyFunctionType) {
return Ternary.True;
}
if (target === anyFunctionType || source === anyFunctionType) return true;
var sourceSignatures = getSignaturesOfType(source, kind);
var targetSignatures = getSignaturesOfType(target, kind);
var result = Ternary.True;
var saveErrorInfo = errorInfo;
outer: for (var i = 0; i < targetSignatures.length; i++) {
var t = targetSignatures[i];
@@ -3636,7 +3606,9 @@ module ts {
for (var j = 0; j < sourceSignatures.length; j++) {
var s = sourceSignatures[j];
if (!s.hasStringLiterals || source.flags & TypeFlags.FromSignature) {
if (signatureRelatedTo(s, t, localErrors)) {
var related = signatureRelatedTo(s, t, localErrors);
if (related) {
result &= related;
errorInfo = saveErrorInfo;
continue outer;
}
@@ -3644,18 +3616,18 @@ module ts {
localErrors = false;
}
}
return false;
return Ternary.False;
}
}
return true;
return result;
}
function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): boolean {
function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary {
if (source === target) {
return true;
return Ternary.True;
}
if (!target.hasRestParameter && source.minArgumentCount > target.parameters.length) {
return false;
return Ternary.False;
}
var sourceMax = source.parameters.length;
var targetMax = target.parameters.length;
@@ -3680,45 +3652,52 @@ module ts {
// M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N
source = getErasedSignature(source);
target = getErasedSignature(target);
var result = Ternary.True;
for (var i = 0; i < checkCount; i++) {
var s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source);
var t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target);
var saveErrorInfo = errorInfo;
if (!isRelatedTo(s, t, reportErrors)) {
if (!isRelatedTo(t, s, false)) {
var related = isRelatedTo(s, t, reportErrors);
if (!related) {
related = isRelatedTo(t, s, false);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Types_of_parameters_0_and_1_are_incompatible,
source.parameters[i < sourceMax ? i : sourceMax].name,
target.parameters[i < targetMax ? i : targetMax].name);
}
return false;
return Ternary.False;
}
errorInfo = saveErrorInfo;
}
result &= related;
}
var t = getReturnTypeOfSignature(target);
if (t === voidType) return true;
if (t === voidType) return result;
var s = getReturnTypeOfSignature(source);
return isRelatedTo(s, t, reportErrors);
return result & isRelatedTo(s, t, reportErrors);
}
function signaturesIdenticalTo(source: ObjectType, target: ObjectType, kind: SignatureKind, reportErrors: boolean): boolean {
function signaturesIdenticalTo(source: ObjectType, target: ObjectType, kind: SignatureKind): Ternary {
var sourceSignatures = getSignaturesOfType(source, kind);
var targetSignatures = getSignaturesOfType(target, kind);
if (sourceSignatures.length !== targetSignatures.length) {
return false;
return Ternary.False;
}
var result = Ternary.True;
for (var i = 0, len = sourceSignatures.length; i < len; ++i) {
if (!compareSignatures(sourceSignatures[i], targetSignatures[i], /*compareReturnTypes*/ true, isRelatedTo)) {
return false;
var related = compareSignatures(sourceSignatures[i], targetSignatures[i], /*compareReturnTypes*/ true, isRelatedTo);
if (!related) {
return Ternary.False;
}
result &= related;
}
return true;
return result;
}
function stringIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
function stringIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return indexTypesIdenticalTo(IndexKind.String, source, target, reportErrors);
return indexTypesIdenticalTo(IndexKind.String, source, target);
}
var targetType = getIndexTypeOfType(target, IndexKind.String);
if (targetType) {
@@ -3727,21 +3706,23 @@ module ts {
if (reportErrors) {
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
}
return false;
return Ternary.False;
}
if (!isRelatedTo(sourceType, targetType, reportErrors)) {
var related = isRelatedTo(sourceType, targetType, reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Index_signatures_are_incompatible);
}
return false;
return Ternary.False;
}
return related;
}
return true;
return Ternary.True;
}
function numberIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
function numberIndexTypesRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean): Ternary {
if (relation === identityRelation) {
return indexTypesIdenticalTo(IndexKind.Number, source, target, reportErrors);
return indexTypesIdenticalTo(IndexKind.Number, source, target);
}
var targetType = getIndexTypeOfType(target, IndexKind.Number);
if (targetType) {
@@ -3751,53 +3732,93 @@ module ts {
if (reportErrors) {
reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source));
}
return false;
return Ternary.False;
}
if (sourceStringType && sourceNumberType) {
// If we know for sure we're testing both string and numeric index types then only report errors from the second one
var compatible = isRelatedTo(sourceStringType, targetType, false) || isRelatedTo(sourceNumberType, targetType, reportErrors);
var related = isRelatedTo(sourceStringType, targetType, false) || isRelatedTo(sourceNumberType, targetType, reportErrors);
}
else {
var compatible = isRelatedTo(sourceStringType || sourceNumberType, targetType, reportErrors);
var related = isRelatedTo(sourceStringType || sourceNumberType, targetType, reportErrors);
}
if (!compatible) {
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Index_signatures_are_incompatible);
}
return false;
return Ternary.False;
}
return related;
}
return true;
return Ternary.True;
}
function indexTypesIdenticalTo(indexKind: IndexKind, source: ObjectType, target: ObjectType, reportErrors: boolean): boolean {
function indexTypesIdenticalTo(indexKind: IndexKind, source: ObjectType, target: ObjectType): Ternary {
var targetType = getIndexTypeOfType(target, indexKind);
var sourceType = getIndexTypeOfType(source, indexKind);
return (!sourceType && !targetType) || (sourceType && targetType && isRelatedTo(sourceType, targetType, reportErrors));
if (!sourceType && !targetType) {
return Ternary.True;
}
if (sourceType && targetType) {
return isRelatedTo(sourceType, targetType);
}
return Ternary.False;
}
}
function compareSignatures(source: Signature, target: Signature, compareReturnTypes: boolean, compareTypes: (s: Type, t: Type) => boolean): boolean {
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
return compareProperties(sourceProp, targetProp, compareTypes) !== 0;
}
function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary {
// Two members are considered identical when
// - they are public properties with identical names, optionality, and types,
// - they are private or protected properties originating in the same declaration and having identical types
if (sourceProp === targetProp) {
return Ternary.True;
}
var sourcePropAccessibility = getDeclarationFlagsFromSymbol(sourceProp) & (NodeFlags.Private | NodeFlags.Protected);
var targetPropAccessibility = getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected);
if (sourcePropAccessibility !== targetPropAccessibility) {
return Ternary.False;
}
if (sourcePropAccessibility) {
if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) {
return Ternary.False;
}
return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
}
else {
if (isOptionalProperty(sourceProp) !== isOptionalProperty(targetProp)) {
return Ternary.False;
}
return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp));
}
}
function compareSignatures(source: Signature, target: Signature, compareReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
if (source === target) {
return true;
return Ternary.True;
}
if (source.parameters.length !== target.parameters.length ||
source.minArgumentCount !== target.minArgumentCount ||
source.hasRestParameter !== target.hasRestParameter) {
return false;
return Ternary.False;
}
var result = Ternary.True;
if (source.typeParameters && target.typeParameters) {
if (source.typeParameters.length !== target.typeParameters.length) {
return false;
return Ternary.False;
}
for (var i = 0, len = source.typeParameters.length; i < len; ++i) {
if (!compareTypes(source.typeParameters[i], target.typeParameters[i])) {
return false;
var related = compareTypes(source.typeParameters[i], target.typeParameters[i]);
if (!related) {
return Ternary.False;
}
result &= related;
}
}
else if (source.typeParameters || source.typeParameters) {
return false;
return Ternary.False;
}
// Spec 1.0 Section 3.8.3 & 3.8.4:
// M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N
@@ -3806,11 +3827,16 @@ module ts {
for (var i = 0, len = source.parameters.length; i < len; i++) {
var s = source.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]);
var t = target.hasRestParameter && i === len - 1 ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]);
if (!compareTypes(s, t)) {
return false;
var related = compareTypes(s, t);
if (!related) {
return Ternary.False;
}
result &= related;
}
return !compareReturnTypes || compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
if (compareReturnTypes) {
result &= compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
}
return result;
}
function isSupertypeOfEach(candidate: Type, types: Type[]): boolean {
@@ -4859,7 +4885,7 @@ module ts {
if (!result) {
result = signature;
}
else if (!compareSignatures(result, signature, /*compareReturnTypes*/ true, isTypeIdenticalTo)) {
else if (!compareSignatures(result, signature, /*compareReturnTypes*/ true, compareTypes)) {
return undefined;
}
}
@@ -5251,7 +5277,7 @@ module ts {
return typeArgumentsAreAssignable;
}
function checkApplicableSignature(node: CallExpression, signature: Signature, relation: Map<boolean>, excludeArgument: boolean[], reportErrors: boolean) {
function checkApplicableSignature(node: CallExpression, signature: Signature, relation: Map<Ternary>, excludeArgument: boolean[], reportErrors: boolean) {
if (node.arguments) {
for (var i = 0; i < node.arguments.length; i++) {
var arg = node.arguments[i];
@@ -5389,7 +5415,7 @@ module ts {
return resolveErrorCall(node);
function chooseOverload(candidates: Signature[], relation: Map<boolean>, excludeArgument: boolean[]) {
function chooseOverload(candidates: Signature[], relation: Map<Ternary>, excludeArgument: boolean[]) {
for (var i = 0; i < candidates.length; i++) {
if (!signatureHasCorrectArity(node, candidates[i])) {
continue;
@@ -7574,6 +7600,43 @@ module ts {
return true;
}
function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean {
if (!type.baseTypes.length || type.baseTypes.length === 1) {
return true;
}
var seen: Map<{ prop: Symbol; containingType: Type }> = {};
forEach(type.declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; });
var ok = true;
for (var i = 0, len = type.baseTypes.length; i < len; ++i) {
var base = type.baseTypes[i];
var properties = getPropertiesOfObjectType(base);
for (var j = 0, proplen = properties.length; j < proplen; ++j) {
var prop = properties[j];
if (!hasProperty(seen, prop.name)) {
seen[prop.name] = { prop: prop, containingType: base };
}
else {
var existing = seen[prop.name];
var isInheritedProperty = existing.containingType !== type;
if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) {
ok = false;
var typeName1 = typeToString(existing.containingType);
var typeName2 = typeToString(base);
var errorInfo = chainDiagnosticMessages(undefined, Diagnostics.Named_properties_0_of_types_1_and_2_are_not_identical, prop.name, typeName1, typeName2);
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2);
addDiagnostic(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo, program.getCompilerHost().getNewLine()));
}
}
}
}
return ok;
}
function checkInterfaceDeclaration(node: InterfaceDeclaration) {
checkTypeParameters(node.typeParameters);
if (fullTypeCheck) {

View File

@@ -1,6 +1,17 @@
/// <reference path="types.ts"/>
module ts {
// Ternary values are defined such that
// x & y is False if either x or y is False.
// x & y is Maybe if either x or y is Maybe, but neither x or y is False.
// x & y is True if x and y are both True.
export enum Ternary {
False = 0,
Maybe = 1,
True = -1
}
export interface Map<T> {
[index: string]: T;
}