Support "-Xno-check-impl" argument, check only real declarations
Use "-Xno-check-impl" to suppress checking whether the platform declaration implementation has the "impl" modifier. Do not check presence of fake overrides from platform class in the impl class, otherwise there would be a lot of errors about the fact that equals/hashCode/toString are not marked with the "impl" modifier
This commit is contained in:
+3
@@ -67,6 +67,9 @@ public abstract class CommonCompilerArguments {
|
||||
@Argument(value = "Xmulti-platform", description = "Enable experimental language support for multi-platform projects")
|
||||
public boolean multiPlatform;
|
||||
|
||||
@Argument(value = "Xno-check-impl", description = "Do not check presence of 'impl' modifier in multi-platform projects")
|
||||
public boolean noCheckImpl;
|
||||
|
||||
@Argument(value = "P", description = "Pass an option to a plugin")
|
||||
@ValueDescription(PLUGIN_OPTION_FORMAT)
|
||||
public String[] pluginOptions;
|
||||
|
||||
@@ -281,6 +281,9 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
|
||||
if (arguments.multiPlatform) {
|
||||
extraLanguageFeatures.add(LanguageFeature.MultiPlatformProjects);
|
||||
}
|
||||
if (arguments.noCheckImpl) {
|
||||
extraLanguageFeatures.add(LanguageFeature.MultiPlatformDoNotCheckImpl);
|
||||
}
|
||||
|
||||
configuration.put(
|
||||
CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
|
||||
|
||||
+32
-13
@@ -56,7 +56,8 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
if (descriptor !is MemberDescriptor) return
|
||||
|
||||
if (descriptor.isPlatform && declaration.hasModifier(KtTokens.PLATFORM_KEYWORD)) {
|
||||
checkPlatformDeclarationHasDefinition(declaration, descriptor, diagnosticHolder)
|
||||
val checkImpl = !languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformDoNotCheckImpl)
|
||||
checkPlatformDeclarationHasDefinition(declaration, descriptor, diagnosticHolder, checkImpl)
|
||||
}
|
||||
else if (descriptor.isImpl && declaration.hasModifier(KtTokens.IMPL_KEYWORD)) {
|
||||
checkImplementationHasPlatformDeclaration(declaration, descriptor, diagnosticHolder)
|
||||
@@ -64,7 +65,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
}
|
||||
|
||||
private fun checkPlatformDeclarationHasDefinition(
|
||||
reportOn: KtDeclaration, descriptor: MemberDescriptor, diagnosticHolder: DiagnosticSink
|
||||
reportOn: KtDeclaration, descriptor: MemberDescriptor, diagnosticHolder: DiagnosticSink, checkImpl: Boolean
|
||||
) {
|
||||
val compatibility = when (descriptor) {
|
||||
is CallableMemberDescriptor -> {
|
||||
@@ -73,7 +74,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
// TODO: support non-source definitions (e.g. from Java)
|
||||
DescriptorToSourceUtils.getSourceFromDescriptor(impl) is KtElement
|
||||
}.groupBy { impl ->
|
||||
areCompatibleCallables(descriptor, impl)
|
||||
areCompatibleCallables(descriptor, impl, checkImpl)
|
||||
}
|
||||
}
|
||||
is ClassDescriptor -> {
|
||||
@@ -81,7 +82,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
descriptor != impl &&
|
||||
DescriptorToSourceUtils.getSourceFromDescriptor(impl) is KtElement
|
||||
}.groupBy { impl ->
|
||||
areCompatibleClassifiers(descriptor, impl)
|
||||
areCompatibleClassifiers(descriptor, impl, checkImpl)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
@@ -102,7 +103,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
findClassifiersFromTheSameModule().firstOrNull { declaration ->
|
||||
this != declaration &&
|
||||
declaration is ClassDescriptor && declaration.isPlatform &&
|
||||
areCompatibleClassifiers(declaration, this) == Compatible
|
||||
areCompatibleClassifiers(declaration, this, checkImpl = false) == Compatible
|
||||
} as? ClassDescriptor
|
||||
|
||||
val hasDeclaration = when (descriptor) {
|
||||
@@ -116,7 +117,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
candidates.any { declaration ->
|
||||
descriptor != declaration &&
|
||||
declaration.isPlatform &&
|
||||
areCompatibleCallables(declaration, descriptor) == Compatible
|
||||
areCompatibleCallables(declaration, descriptor, checkImpl = false) == Compatible
|
||||
}
|
||||
}
|
||||
is ClassifierDescriptor -> descriptor.findDeclarationForClass() != null
|
||||
@@ -202,6 +203,8 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
object TypeParameterVariance : Incompatible("declaration-site variances of type parameters are different")
|
||||
object TypeParameterReified : Incompatible("some type parameter is reified in one declaration and non-reified in the other")
|
||||
|
||||
object NoImpl : Incompatible("the implementation is not marked with the 'impl' modifier (-Xno-check-impl)")
|
||||
|
||||
object Unknown : Incompatible(null)
|
||||
}
|
||||
|
||||
@@ -209,7 +212,12 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
}
|
||||
|
||||
// a is the declaration in common code, b is the definition in the platform-specific code
|
||||
private fun areCompatibleCallables(a: CallableMemberDescriptor, b: CallableMemberDescriptor, parentSubstitutor: Substitutor? = null): Compatibility {
|
||||
private fun areCompatibleCallables(
|
||||
a: CallableMemberDescriptor,
|
||||
b: CallableMemberDescriptor,
|
||||
checkImpl: Boolean,
|
||||
parentSubstitutor: Substitutor? = null
|
||||
): Compatibility {
|
||||
assert(a.name == b.name) { "This function should be invoked only for declarations with the same name: $a, $b" }
|
||||
assert(a.containingDeclaration is ClassDescriptor == b.containingDeclaration is ClassDescriptor) {
|
||||
"This function should be invoked only for declarations in the same kind of container (both members or both top level): $a, $b"
|
||||
@@ -250,7 +258,7 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
else -> throw AssertionError("Unsupported declarations: $a, $b")
|
||||
}
|
||||
|
||||
// TODO: check 'impl' modifier
|
||||
if (checkImpl && !b.isImpl) return Incompatible.NoImpl
|
||||
|
||||
return Compatible
|
||||
}
|
||||
@@ -278,15 +286,17 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
return Compatible
|
||||
}
|
||||
|
||||
private fun areCompatibleClassifiers(a: ClassDescriptor, other: ClassifierDescriptor): Compatibility {
|
||||
private fun areCompatibleClassifiers(a: ClassDescriptor, other: ClassifierDescriptor, checkImpl: Boolean): Compatibility {
|
||||
assert(a.fqNameUnsafe == other.fqNameUnsafe) { "This function should be invoked only for declarations with the same name: $a, $other" }
|
||||
|
||||
var parentSubstitutor: Substitutor? = null
|
||||
var implTypealias = false
|
||||
|
||||
val b = when (other) {
|
||||
is ClassDescriptor -> other
|
||||
is TypeAliasDescriptor -> {
|
||||
val classDescriptor = other.classDescriptor ?: return Compatible // do not report extra error on erroneous typealias
|
||||
implTypealias = true
|
||||
// If a platform class test.C is implemented by a typealias test.C = test.CImpl, we must now state that any occurrence
|
||||
// of the type "test.C" in the platform class scope should be replaced with "test.CImpl".
|
||||
// Otherwise the types would not be equal and e.g. test.C's constructor is not going to be found in test.CImpl's scope.
|
||||
@@ -317,20 +327,29 @@ class PlatformImplDeclarationChecker : DeclarationChecker {
|
||||
|
||||
if (!b.typeConstructor.supertypes.containsAll(a.typeConstructor.supertypes.map(substitutor))) return Incompatible.Supertypes
|
||||
|
||||
areCompatibleClassScopes(a, b, substitutor).let { if (it != Compatible) return it }
|
||||
areCompatibleClassScopes(a, b, checkImpl && !implTypealias, substitutor).let { if (it != Compatible) return it }
|
||||
|
||||
// TODO: check 'impl' modifier
|
||||
if (checkImpl && !b.isImpl && !implTypealias) return Incompatible.NoImpl
|
||||
|
||||
return Compatible
|
||||
}
|
||||
|
||||
private fun areCompatibleClassScopes(a: ClassDescriptor, b: ClassDescriptor, substitutor: Substitutor): Compatibility {
|
||||
private fun areCompatibleClassScopes(
|
||||
a: ClassDescriptor,
|
||||
b: ClassDescriptor,
|
||||
checkImpl: Boolean,
|
||||
substitutor: Substitutor
|
||||
): Compatibility {
|
||||
val unimplemented = arrayListOf<Pair<CallableMemberDescriptor, Map<Incompatible, MutableCollection<CallableMemberDescriptor>>>>()
|
||||
|
||||
val bMembersByName = b.getMembers().groupBy { it.name }
|
||||
|
||||
outer@ for (aMember in a.getMembers()) {
|
||||
val mapping = bMembersByName[aMember.name].orEmpty().keysToMap { bMember -> areCompatibleCallables(aMember, bMember, substitutor) }
|
||||
if (!aMember.kind.isReal) continue
|
||||
|
||||
val mapping = bMembersByName[aMember.name].orEmpty().keysToMap { bMember ->
|
||||
areCompatibleCallables(aMember, bMember, checkImpl, substitutor)
|
||||
}
|
||||
if (mapping.values.any { it == Compatible }) continue
|
||||
|
||||
val incompatibilityMap = mutableMapOf<Incompatible, MutableCollection<CallableMemberDescriptor>>()
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ where advanced options include:
|
||||
-Xrepeat <count> Repeat compilation (for performance analysis)
|
||||
-Xplugin <path> Load plugins from the given classpath
|
||||
-Xmulti-platform Enable experimental language support for multi-platform projects
|
||||
-Xno-check-impl Do not check presence of 'impl' modifier in multi-platform projects
|
||||
|
||||
Advanced options are non-standard and may be changed or removed without any notice.
|
||||
OK
|
||||
+1
@@ -17,6 +17,7 @@ where advanced options include:
|
||||
-Xrepeat <count> Repeat compilation (for performance analysis)
|
||||
-Xplugin <path> Load plugins from the given classpath
|
||||
-Xmulti-platform Enable experimental language support for multi-platform projects
|
||||
-Xno-check-impl Do not check presence of 'impl' modifier in multi-platform projects
|
||||
|
||||
Advanced options are non-standard and may be changed or removed without any notice.
|
||||
OK
|
||||
@@ -0,0 +1,7 @@
|
||||
platform open class Base {
|
||||
fun base()
|
||||
}
|
||||
|
||||
platform class Derived : Base {
|
||||
fun derived()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
impl open class Base {
|
||||
impl fun base() {}
|
||||
}
|
||||
|
||||
impl class Derived : Base() {
|
||||
impl fun derived() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Common --
|
||||
Exit code: OK
|
||||
Output:
|
||||
|
||||
-- JVM --
|
||||
Exit code: OK
|
||||
Output:
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
impl class Printer {
|
||||
fun print(message: String) {
|
||||
impl fun print(message: String) {
|
||||
println("JS says: " + message)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
impl class Printer {
|
||||
fun print(message: String) {
|
||||
impl fun print(message: String) {
|
||||
println("JVM says: " + message)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -98,6 +98,12 @@ public class MultiPlatformIntegrationTestGenerated extends AbstractMultiPlatform
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("fakeOverrides")
|
||||
public void testFakeOverrides() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/multiplatform/classScopes/fakeOverrides/");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("functionIncorrectSignature")
|
||||
public void testFunctionIncorrectSignature() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/multiplatform/classScopes/functionIncorrectSignature/");
|
||||
|
||||
@@ -36,6 +36,7 @@ enum class LanguageFeature(val sinceVersion: LanguageVersion?) {
|
||||
|
||||
// Experimental features
|
||||
MultiPlatformProjects(null),
|
||||
MultiPlatformDoNotCheckImpl(null),
|
||||
;
|
||||
|
||||
val presentableText: String
|
||||
|
||||
Reference in New Issue
Block a user