Improve header/impl mismatch diagnostic messages

Try to report most mismatch errors on the 'impl' declaration. Only
report a mismatch error on the 'header' declaration if no error would be
otherwise reported on any 'impl' declaration in the compilation unit.
Also render declaration kind in the message

 #KT-18447 In Progress
This commit is contained in:
Alexander Udalov
2017-07-17 20:54:23 +03:00
parent 472959aca1
commit 74ba0080b1
34 changed files with 372 additions and 494 deletions
@@ -562,7 +562,9 @@ public interface Errors {
DiagnosticFactory3<KtDeclaration, MemberDescriptor, ModuleDescriptor,
Map<HeaderImplDeclarationChecker.Compatibility.Incompatible, Collection<MemberDescriptor>>> HEADER_WITHOUT_IMPLEMENTATION =
DiagnosticFactory3.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<PsiElement> IMPLEMENTATION_WITHOUT_HEADER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<KtDeclaration, MemberDescriptor,
Map<HeaderImplDeclarationChecker.Compatibility.Incompatible, Collection<MemberDescriptor>>> IMPLEMENTATION_WITHOUT_HEADER =
DiagnosticFactory2.create(ERROR, DECLARATION_SIGNATURE);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -270,9 +270,10 @@ public class DefaultErrorMessages {
MAP.put(IMPL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE, "Right-hand side of 'impl' type alias cannot contain use-site variance or star projections");
MAP.put(IMPL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION, "Type arguments in the right-hand side of 'impl' type alias should be its type parameters in the same order, e.g. 'impl typealias Foo<A, B> = Bar<A, B>'");
MAP.put(HEADER_WITHOUT_IMPLEMENTATION, "Header declaration ''{0}'' has no implementation in module{1}{2}", NAME,
MAP.put(HEADER_WITHOUT_IMPLEMENTATION, "''header'' {0} has no implementation in module{1}{2}", DECLARATION_NAME_WITH_KIND,
PLATFORM, PlatformIncompatibilityDiagnosticRenderer.INSTANCE);
MAP.put(IMPLEMENTATION_WITHOUT_HEADER, "Modifier 'impl' is only applicable to members that are initially declared in platform-independent code");
MAP.put(IMPLEMENTATION_WITHOUT_HEADER, "''impl'' {0} has no corresponding ''header'' declaration{1}", DECLARATION_NAME_WITH_KIND,
PlatformIncompatibilityDiagnosticRenderer.INSTANCE);
MAP.put(PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, "Projections are not allowed on type arguments of functions and properties");
MAP.put(SUPERTYPE_NOT_INITIALIZED, "This type has a constructor, and thus must be initialized here");
@@ -93,19 +93,19 @@ object Renderers {
}
@JvmField val DECLARATION_NAME_WITH_KIND = Renderer<DeclarationDescriptor> {
val declarationKindWithSpace = when (it) {
is PackageFragmentDescriptor -> "package "
is ClassDescriptor -> "${it.renderKind()} "
is TypeAliasDescriptor -> "typealias "
is ConstructorDescriptor -> "constructor "
is TypeAliasConstructorDescriptor -> "typealias constructor "
is PropertyGetterDescriptor -> "property getter "
is PropertySetterDescriptor -> "property setter "
is FunctionDescriptor -> "function "
is PropertyDescriptor -> "property "
val name = it.name.asString()
when (it) {
is PackageFragmentDescriptor -> "package '$name'"
is ClassDescriptor -> "${it.renderKind()} '$name'"
is TypeAliasDescriptor -> "typealias '$name'"
is TypeAliasConstructorDescriptor -> "constructor of '${it.typeAliasDescriptor.name.asString()}'"
is ConstructorDescriptor -> "constructor of '${it.constructedClass.name.asString()}'"
is PropertyGetterDescriptor -> "getter of property '${it.correspondingProperty.name.asString()}'"
is PropertySetterDescriptor -> "setter of property '${it.correspondingProperty.name.asString()}'"
is FunctionDescriptor -> "function '$name'"
is PropertyDescriptor -> "property '$name'"
else -> throw AssertionError("Unexpected declaration kind: $it")
}
"$declarationKindWithSpace'${it.name.asString()}'"
}
@JvmField val NAME_OF_CONTAINING_DECLARATION_OR_FILE = Renderer<DeclarationDescriptor> {
@@ -77,9 +77,16 @@ object HeaderImplDeclarationChecker : DeclarationChecker {
platformModule: ModuleDescriptor,
checkImpl: Boolean
) {
val compatibility = findImplForHeader(descriptor, platformModule, checkImpl)
val compatibility = findImplForHeader(descriptor, platformModule, checkImpl) ?: return
if (compatibility != null && Compatible !in compatibility) {
val shouldReportError =
compatibility.isEmpty() ||
Compatible !in compatibility && compatibility.values.flatMapTo(hashSetOf()) { it }.all { impl ->
val headers = findHeaderForImpl(impl, descriptor.module)
headers != null && Compatible in headers.keys
}
if (shouldReportError) {
assert(compatibility.keys.all { it is Incompatible })
@Suppress("UNCHECKED_CAST")
val incompatibility = compatibility as Map<Incompatible, Collection<MemberDescriptor>>
@@ -120,12 +127,13 @@ object HeaderImplDeclarationChecker : DeclarationChecker {
// Using the platform module instead of the common module is sort of fine here because the former always depends on the latter.
// However, it would be clearer to find the common module this platform module implements and look for headers there instead.
// TODO: use common module here
val compatibility = findHeaderForImpl(descriptor, descriptor.module)
val compatibility = findHeaderForImpl(descriptor, descriptor.module) ?: return
if (compatibility != null && Compatible !in compatibility) {
if (Compatible !in compatibility) {
assert(compatibility.keys.all { it is Incompatible })
// TODO: do not report this error for members which are "almost compatible" with some header declarations
diagnosticHolder.report(Errors.IMPLEMENTATION_WITHOUT_HEADER.on(reportOn.modifierList!!.getModifier(KtTokens.IMPL_KEYWORD)!!))
@Suppress("UNCHECKED_CAST")
val incompatibility = compatibility as Map<Incompatible, Collection<MemberDescriptor>>
diagnosticHolder.report(Errors.IMPLEMENTATION_WITHOUT_HEADER.on(reportOn, descriptor, incompatibility))
}
}