Offer per-member diagnostics for incorrectly implemented inherited members (#21036)

* Offer per-member diagnostics for incorrectly implemented inherited members on classes

* Revise error message, make containingChain a thunk

* Fix typo in comment
This commit is contained in:
Wesley Wigham
2018-01-09 10:20:07 -08:00
committed by GitHub
parent ba979fa537
commit fdd8a52240
40 changed files with 799 additions and 670 deletions

View File

@@ -9169,7 +9169,7 @@ namespace ts {
return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1);
}
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean {
function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
}
@@ -9177,7 +9177,7 @@ namespace ts {
* This is *not* a bi-directional relationship.
* If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'.
*/
function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean {
function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain);
}
@@ -9511,7 +9511,7 @@ namespace ts {
relation: Map<RelationComparisonResult>,
errorNode: Node,
headMessage?: DiagnosticMessage,
containingMessageChain?: DiagnosticMessageChain): boolean {
containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean {
let errorInfo: DiagnosticMessageChain;
let maybeKeys: string[];
@@ -9531,7 +9531,10 @@ namespace ts {
}
else if (errorInfo) {
if (containingMessageChain) {
errorInfo = concatenateDiagnosticMessageChains(containingMessageChain, errorInfo);
const chain = containingMessageChain();
if (chain) {
errorInfo = concatenateDiagnosticMessageChains(chain, errorInfo);
}
}
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo));
@@ -16675,7 +16678,7 @@ namespace ts {
const constraint = getConstraintOfTypeParameter(typeParameters[i]);
if (!constraint) continue;
const errorInfo = reportErrors && headMessage && chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1);
const errorInfo = reportErrors && headMessage && (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1));
const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1;
if (!mapper) {
mapper = createTypeMapper(typeParameters, typeArgumentTypes);
@@ -19697,7 +19700,7 @@ namespace ts {
Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter);
}
else {
const leadingError = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type);
const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type);
checkTypeAssignableTo(typePredicate.type,
getTypeOfNode(parent.parameters[typePredicate.parameterIndex]),
node.type,
@@ -20984,7 +20987,7 @@ namespace ts {
expectedReturnType,
node,
headMessage,
errorInfo);
() => errorInfo);
}
/**
@@ -22907,7 +22910,10 @@ namespace ts {
}
}
}
checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name || node, Diagnostics.Class_0_incorrectly_extends_base_class_1);
const baseWithThis = getTypeWithThisArgument(baseType, type.thisType);
if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) {
issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1);
}
checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node,
Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1);
if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) {
@@ -22939,12 +22945,13 @@ namespace ts {
const t = getTypeFromTypeNode(typeRefNode);
if (t !== unknownType) {
if (isValidBaseType(t)) {
checkTypeAssignableTo(typeWithThis,
getTypeWithThisArgument(t, type.thisType),
node.name || node,
t.symbol && t.symbol.flags & SymbolFlags.Class ?
Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass :
Diagnostics.Class_0_incorrectly_implements_interface_1);
const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ?
Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass :
Diagnostics.Class_0_incorrectly_implements_interface_1;
const baseWithThis = getTypeWithThisArgument(t, type.thisType);
if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) {
issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag);
}
}
else {
error(typeRefNode, Diagnostics.A_class_may_only_implement_another_class_or_interface);
@@ -22961,6 +22968,37 @@ namespace ts {
}
}
function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) {
// iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible
let issuedMemberError = false;
for (const member of node.members) {
if (hasStaticModifier(member)) {
continue;
}
const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member);
if (declaredProp) {
const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName);
const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName);
if (prop && baseProp) {
const rootChain = () => chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
unescapeLeadingUnderscores(declaredProp.escapedName),
typeToString(typeWithThis),
typeToString(getTypeOfSymbol(baseProp))
);
if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) {
issuedMemberError = true;
}
}
}
}
if (!issuedMemberError) {
// check again with diagnostics to generate a less-specific error
checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag);
}
}
function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) {
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
if (signatures.length) {

View File

@@ -1404,6 +1404,10 @@
"category": "Error",
"code": 2415
},
"Property '{0}' in type '{1}' is not assignable to the same property in base type '{2}'.": {
"category": "Error",
"code": 2416
},
"Class static side '{0}' incorrectly extends base class static side '{1}'.": {
"category": "Error",
"code": 2417