Elaborate relational errors at least once when reporting them

This commit is contained in:
Ryan Cavanaugh
2015-01-20 14:18:22 -08:00
parent 8d5b65a533
commit fa239326f0
58 changed files with 613 additions and 50 deletions
+24 -12
View File
@@ -3365,9 +3365,9 @@ module ts {
// TYPE CHECKING
var subtypeRelation: Map<boolean> = {};
var assignableRelation: Map<boolean> = {};
var identityRelation: Map<boolean> = {};
var subtypeRelation: Map<RelationComparisonResult> = {};
var assignableRelation: Map<RelationComparisonResult> = {};
var identityRelation: Map<RelationComparisonResult> = {};
function isTypeIdenticalTo(source: Type, target: Type): boolean {
return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined);
@@ -3402,7 +3402,7 @@ module ts {
function checkTypeRelatedTo(
source: Type,
target: Type,
relation: Map<boolean>,
relation: Map<RelationComparisonResult>,
errorNode: Node,
headMessage?: DiagnosticMessage,
containingMessageChain?: DiagnosticMessageChain): boolean {
@@ -3422,6 +3422,14 @@ module ts {
error(errorNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
}
else if (errorInfo) {
// If we already computed this relation, but in a context where we didn't want to report errors (e.g. overload resolution),
// then we'll only have a top-level error (e.g. 'Class X does not implement interface Y') without any details. If this happened,
// request a recompuation to get a complete error message. This will be skipped if we've already done this computation in a context
// where errors were being reported.
if (errorInfo.next === undefined) {
errorInfo = undefined;
isRelatedTo(source, target, errorNode !== undefined, headMessage, true);
}
if (containingMessageChain) {
errorInfo = concatenateDiagnosticMessageChains(containingMessageChain, errorInfo);
}
@@ -3437,7 +3445,7 @@ module ts {
// 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 {
function isRelatedTo(source: Type, target: Type, reportErrors?: boolean, headMessage?: DiagnosticMessage, mustRecompute = false): Ternary {
var result: Ternary;
// 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 Ternary.True;
@@ -3504,7 +3512,7 @@ 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 &&
(result = objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors))) {
(result = objectTypeRelatedTo(sourceOrApparentType, <ObjectType>target, reportStructuralErrors, mustRecompute))) {
errorInfo = saveErrorInfo;
return result;
}
@@ -3595,14 +3603,16 @@ 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): Ternary {
function objectTypeRelatedTo(source: ObjectType, target: ObjectType, reportErrors: boolean, elaborateErrors = false): Ternary {
if (overflow) {
return Ternary.False;
}
var id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id;
var related = relation[id];
if (related !== undefined) {
return related ? Ternary.True : Ternary.False;
if (!elaborateErrors || related === RelationComparisonResult.FailedAndReported) {
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
}
if (depth > 0) {
for (var i = 0; i < depth; i++) {
@@ -3654,12 +3664,14 @@ module ts {
var maybeCache = maybeStack[depth];
// If result is definitely true, copy assumptions to global cache, else copy to next level up
var destinationCache = result === Ternary.True || depth === 0 ? relation : maybeStack[depth - 1];
copyMap(/*source*/maybeCache, /*target*/destinationCache);
for (var key in maybeCache) {
destinationCache[key] = maybeCache[key] ? RelationComparisonResult.Succeeded : RelationComparisonResult.Failed;
}
}
else {
// A false result goes straight into global cache (when something is false under assumptions it
// will also be false without assumptions)
relation[id] = false;
relation[id] = reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed;
}
return result;
}
@@ -5865,7 +5877,7 @@ module ts {
return typeArgumentsAreAssignable;
}
function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map<boolean>, excludeArgument: boolean[], reportErrors: boolean) {
function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var argType: Type;
@@ -6059,7 +6071,7 @@ module ts {
return resolveErrorCall(node);
function chooseOverload(candidates: Signature[], relation: Map<boolean>) {
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>) {
for (var i = 0; i < candidates.length; i++) {
if (!hasCorrectArity(node, args, candidates[i])) {
continue;
+6
View File
@@ -330,6 +330,12 @@ module ts {
HasAggregatedChildData = 1 << 6
}
export const enum RelationComparisonResult {
Succeeded = 0,
Failed = 1,
FailedAndReported = 2
}
export interface Node extends TextRange {
kind: SyntaxKind;
flags: NodeFlags;