Put error spans deep on nested object literals (#25140)

* Add ncie deep elaborations

* Nice stuff

* Modify tuple error to use length error mroe often

* Accept good baselines

* Accept meh baselines

* Fix literal types

* Calculate elaborations like it was the very first time again~

* Use tristate for enum relationship to ensure elaborations are printed at least once

* Update message text, nits

* move some functions back to where they were

* Add test of deep JSX elaboration

* Add elaboration test with parenthesized expressions, comma expressions, and assignments

* Move check to allow elaborations on more anonymous types

* Fix nits

* Add specialized error to elaborations of nonliteral computed named-members

* Update error message
This commit is contained in:
Wesley Wigham
2018-07-03 19:40:58 -07:00
committed by GitHub
parent e4145e3017
commit 84f5aa540e
85 changed files with 1388 additions and 706 deletions

View File

@@ -598,7 +598,7 @@ namespace ts {
const definitelyAssignableRelation = createMap<RelationComparisonResult>();
const comparableRelation = createMap<RelationComparisonResult>();
const identityRelation = createMap<RelationComparisonResult>();
const enumRelation = createMap<boolean>();
const enumRelation = createMap<RelationComparisonResult>();
type TypeSystemEntity = Symbol | Type | Signature;
@@ -10135,8 +10135,163 @@ namespace ts {
return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1);
}
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { error?: Diagnostic }): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject);
}
/**
* Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to
* attempt to issue more specific errors on, for example, specific object literal properties or tuple members.
*/
function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
if (isTypeAssignableTo(source, target)) return true;
if (!elaborateError(expr, source, target)) {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
}
return false;
}
function elaborateError(node: Expression | undefined, source: Type, target: Type): boolean {
if (!node) return false;
switch (node.kind) {
case SyntaxKind.JsxExpression:
case SyntaxKind.ParenthesizedExpression:
return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target);
case SyntaxKind.BinaryExpression:
switch ((node as BinaryExpression).operatorToken.kind) {
case SyntaxKind.EqualsToken:
case SyntaxKind.CommaToken:
return elaborateError((node as BinaryExpression).right, source, target);
}
break;
case SyntaxKind.ObjectLiteralExpression:
return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target);
case SyntaxKind.ArrayLiteralExpression:
return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target);
case SyntaxKind.JsxAttributes:
return elaborateJsxAttributes(node as JsxAttributes, source, target);
}
return false;
}
type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>;
/**
* For every element returned from the iterator, checks that element to issue an error on a property of that element's type
* If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError`
* Otherwise, we issue an error on _every_ element which fail the assignability check
*/
function elaborateElementwise(iterator: ElaborationIterator, source: Type, target: Type) {
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
let reportedError = false;
for (let status = iterator.next(); !status.done; status = iterator.next()) {
const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value;
const sourcePropType = getIndexedAccessType(source, nameType);
const targetPropType = getIndexedAccessType(target, nameType);
if (!isTypeAssignableTo(sourcePropType, targetPropType)) {
const elaborated = next && elaborateError(next, sourcePropType, targetPropType);
if (elaborated) {
reportedError = true;
}
else {
// Issue error on the prop itself, since the prop couldn't elaborate the error
const resultObj: { error?: Diagnostic } = {};
// Use the expression type, if available
const specificSource = next ? checkExpressionForMutableLocation(next, CheckMode.Normal, sourcePropType) : sourcePropType;
const result = checkTypeAssignableTo(specificSource, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
if (result && specificSource !== sourcePropType) {
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
checkTypeAssignableTo(sourcePropType, targetPropType, prop, errorMessage, /*containingChain*/ undefined, resultObj);
}
if (resultObj.error) {
const reportedDiag = resultObj.error;
const propertyName = isTypeUsableAsLateBoundName(nameType) ? getLateBoundNameFromType(nameType) : undefined;
const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined;
let issuedElaboration = false;
if (!targetProp) {
const indexInfo = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) ||
getIndexInfoOfType(target, IndexKind.String) ||
undefined;
if (indexInfo && indexInfo.declaration) {
issuedElaboration = true;
addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature));
}
}
if (!issuedElaboration && (length(targetProp && targetProp.declarations) || length(target.symbol && target.symbol.declarations))) {
addRelatedInfo(reportedDiag, createDiagnosticForNode(
targetProp ? targetProp.declarations[0] : target.symbol.declarations[0],
Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1,
propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType),
typeToString(target)
));
}
}
reportedError = true;
}
}
}
return reportedError;
}
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isJsxSpreadAttribute(prop)) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) };
}
}
function elaborateJsxAttributes(node: JsxAttributes, source: Type, target: Type) {
return elaborateElementwise(generateJsxAttributes(node), source, target);
}
function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator {
const len = length(node.elements);
if (!len) return;
for (let i = 0; i < len; i++) {
// Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature
if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue;
const elem = node.elements[i];
if (isOmittedExpression(elem)) continue;
const nameType = getLiteralType(i);
yield { errorNode: elem, innerExpression: elem, nameType };
}
}
function elaborateArrayLiteral(node: ArrayLiteralExpression, source: Type, target: Type) {
if (isTupleLikeType(source)) {
return elaborateElementwise(generateLimitedTupleElements(node, target), source, target);
}
return false;
}
function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isSpreadAssignment(prop)) continue;
const type = getLiteralTypeFromPropertyName(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique);
if (!type || (type.flags & TypeFlags.Never)) {
continue;
}
switch (prop.kind) {
case SyntaxKind.SetAccessor:
case SyntaxKind.GetAccessor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.ShorthandPropertyAssignment:
yield { errorNode: prop.name, innerExpression: undefined, nameType: type };
break;
case SyntaxKind.PropertyAssignment:
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined };
break;
default:
Debug.assertNever(prop);
}
}
}
function elaborateObjectLiteral(node: ObjectLiteralExpression, source: Type, target: Type) {
return elaborateElementwise(generateObjectLiteralElements(node), source, target);
}
/**
@@ -10351,11 +10506,11 @@ namespace ts {
}
const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol);
const relation = enumRelation.get(id);
if (relation !== undefined) {
return relation;
if (relation !== undefined && !(relation === RelationComparisonResult.Failed && errorReporter)) {
return relation === RelationComparisonResult.Succeeded;
}
if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) {
enumRelation.set(id, false);
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
return false;
}
const targetEnumType = getTypeOfSymbol(targetSymbol);
@@ -10366,13 +10521,16 @@ namespace ts {
if (errorReporter) {
errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property),
typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType));
enumRelation.set(id, RelationComparisonResult.FailedAndReported);
}
else {
enumRelation.set(id, RelationComparisonResult.Failed);
}
enumRelation.set(id, false);
return false;
}
}
}
enumRelation.set(id, true);
enumRelation.set(id, RelationComparisonResult.Succeeded);
return true;
}
@@ -10457,7 +10615,9 @@ namespace ts {
relation: Map<RelationComparisonResult>,
errorNode: Node | undefined,
headMessage?: DiagnosticMessage,
containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
containingMessageChain?: () => DiagnosticMessageChain | undefined,
errorOutputContainer?: { error?: Diagnostic }
): boolean {
let errorInfo: DiagnosticMessageChain | undefined;
let maybeKeys: string[];
@@ -10497,7 +10657,11 @@ namespace ts {
}
}
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation)); // TODO: GH#18217
const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation);
if (errorOutputContainer) {
errorOutputContainer.error = diag;
}
diagnostics.add(diag); // TODO: GH#18217
}
return result !== Ternary.False;
@@ -11019,9 +11183,8 @@ namespace ts {
const related = relation.get(id);
if (related !== undefined) {
if (reportErrors && related === RelationComparisonResult.Failed) {
// We are elaborating errors and the cached result is an unreported failure. Record the result as a reported
// failure and continue computing the relation such that errors get reported.
relation.set(id, RelationComparisonResult.FailedAndReported);
// We are elaborating errors and the cached result is an unreported failure. The result will be reported
// as a failure, and should be updated as a reported failure by the bottom of this function.
}
else {
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
@@ -15636,7 +15799,7 @@ namespace ts {
const discriminatingType = checkExpression(prop.initializer);
for (const type of (contextualType as UnionType).types) {
const targetType = getTypeOfPropertyOfType(type, prop.symbol.escapedName);
if (targetType && checkTypeAssignableTo(discriminatingType, targetType, /*errorNode*/ undefined)) {
if (targetType && isTypeAssignableTo(discriminatingType, targetType)) {
if (match) {
if (type === match) continue; // Finding multiple fields which discriminate to the same type is fine
match = undefined;
@@ -17153,30 +17316,7 @@ namespace ts {
}
}
else if (!isSourceAttributeTypeAssignableToTarget) {
// Assignability failure - check each prop individually, and if that fails, fall back on the bad error span
if (length(openingLikeElement.attributes.properties)) {
let reportedError = false;
for (const prop of openingLikeElement.attributes.properties) {
if (isJsxSpreadAttribute(prop)) continue;
const name = idText(prop.name);
const sourcePropType = getIndexedAccessType(sourceAttributesType, getLiteralType(name));
const targetPropType = getIndexedAccessType(targetAttributesType, getLiteralType(name));
const rootChain = () => chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Types_of_property_0_are_incompatible,
name,
);
if (!checkTypeAssignableTo(sourcePropType, targetPropType, prop, /*headMessage*/ undefined, rootChain)) {
reportedError = true;
}
}
if (reportedError) {
return;
}
}
// Report fallback error on just the component name
checkTypeAssignableTo(sourceAttributesType, targetAttributesType, openingLikeElement.tagName);
checkTypeAssignableToAndOptionallyElaborate(sourceAttributesType, targetAttributesType, openingLikeElement.tagName, openingLikeElement.attributes);
}
}
@@ -20160,10 +20300,10 @@ namespace ts {
if (returnOrPromisedType) {
if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function
const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member);
checkTypeAssignableTo(awaitedType, returnOrPromisedType, node.body);
checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body);
}
else { // Normal function
checkTypeAssignableTo(exprType, returnOrPromisedType, node.body);
checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body);
}
}
}
@@ -20581,7 +20721,7 @@ namespace ts {
Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access :
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access;
if (checkReferenceExpression(target, error)) {
checkTypeAssignableTo(sourceType, targetType, target, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target);
}
return sourceType;
}
@@ -20880,7 +21020,7 @@ namespace ts {
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
checkTypeAssignableTo(valueType, leftType, left, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
}
}
}
@@ -20992,7 +21132,7 @@ namespace ts {
const returnType = getEffectiveReturnTypeNode(func);
if (returnType) {
const signatureElementType = getIteratedTypeOfGenerator(getTypeFromTypeNode(returnType), isAsync) || anyType;
checkTypeAssignableTo(yieldedType, signatureElementType, node.expression || node, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureElementType, node.expression || node, node.expression);
}
// Both yield and yield* expressions have type 'any'
@@ -23713,7 +23853,7 @@ namespace ts {
checkNonNullType(initializerType, node);
}
else {
checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer);
}
checkParameterInitializer(node);
}
@@ -23731,7 +23871,7 @@ namespace ts {
(initializer.properties.length === 0 || isPrototypeAccess(node.name)) &&
hasEntries(symbol.exports);
if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) {
checkTypeAssignableTo(checkExpressionCached(initializer), type, node, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined);
checkParameterInitializer(node);
}
}
@@ -23747,7 +23887,7 @@ namespace ts {
errorNextVariableOrPropertyDeclarationMustHaveSameType(type, node, declarationType);
}
if (node.initializer) {
checkTypeAssignableTo(checkExpressionCached(node.initializer), declarationType, node, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined);
}
if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) {
error(getNameOfDeclaration(symbol.valueDeclaration), Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name));
@@ -23920,7 +24060,7 @@ namespace ts {
// because we accessed properties from anyType, or it may have led to an error inside
// getElementTypeOfIterable.
if (iteratedType) {
checkTypeAssignableTo(iteratedType, leftType, varExpr, /*headMessage*/ undefined);
checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression);
}
}
}
@@ -24364,7 +24504,7 @@ namespace ts {
}
}
else if (func.kind === SyntaxKind.Constructor) {
if (node.expression && !checkTypeAssignableTo(exprType, returnType, node)) {
if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) {
error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class);
}
}
@@ -24380,7 +24520,7 @@ namespace ts {
}
}
else {
checkTypeAssignableTo(exprType, returnType, node);
checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression);
}
}
}

View File

@@ -1452,6 +1452,10 @@
"category": "Error",
"code": 2417
},
"Type of computed property's value is '{0}', which is not assignable to type '{1}'.": {
"category": "Error",
"code": 2418
},
"Class '{0}' incorrectly implements interface '{1}'.": {
"category": "Error",
"code": 2420
@@ -3754,6 +3758,15 @@
"code": 6371
},
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
"category": "Message",
"code": 6500
},
"The expected type comes from this index signature.": {
"category": "Message",
"code": 6501
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
"pretty": true,
"lib": ["es5"],
"lib": ["es2015"],
"target": "es5",
"declaration": true,