diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt b/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt new file mode 100644 index 00000000000..88d7de80da4 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt @@ -0,0 +1,56 @@ +class C { + class prototype + + class length + + class `$metadata$` + + fun constructor() {} +} + +class D { + private class prototype + + private class length + + private class `$metadata$` + + private fun constructor() {} +} + +class E { + @JsName("prototype") + class D + + @JsName("constructor") + fun f() {} +} + +class F { + @JsName("A") + class prototype + + @JsName("B") + class length + + @JsName("f") + fun constructor() {} +} + +class prototype + +class length + +fun constructor() { +} + +fun f() { + class prototype + class length + + fun constructor() {} +} + +external interface Object { + val constructor: Any? +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.txt b/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.txt new file mode 100644 index 00000000000..0864678f7a9 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.txt @@ -0,0 +1,120 @@ +package + +public fun constructor(): kotlin.Unit +public fun f(): kotlin.Unit + +public final class C { + public constructor C() + public final fun constructor(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + public final class `$metadata$` { + public constructor `$metadata$`() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class length { + public constructor length() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + public final class prototype { + public constructor prototype() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class D { + public constructor D() + private final fun constructor(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + private final class `$metadata$` { + public constructor `$metadata$`() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + private final class length { + public constructor length() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + private final class prototype { + public constructor prototype() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class E { + public constructor E() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @kotlin.js.JsName(name = "constructor") public final fun f(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + @kotlin.js.JsName(name = "prototype") public final class D { + public constructor D() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class F { + public constructor F() + @kotlin.js.JsName(name = "f") public final fun constructor(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + @kotlin.js.JsName(name = "B") public final class length { + public constructor length() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + + @kotlin.js.JsName(name = "A") public final class prototype { + public constructor prototype() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public external interface Object { + public abstract val constructor: kotlin.Any? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class length { + public constructor length() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class prototype { + public constructor prototype() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index 3e3634c2df1..533112b4840 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -485,6 +485,12 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes doTest(fileName); } + @TestMetadata("builtinClash.kt") + public void testBuiltinClash() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/builtinClash.kt"); + doTest(fileName); + } + @TestMetadata("classAndFunction.kt") public void testClassAndFunction() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/name/classAndFunction.kt"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index a2b4c6f6621..214374f716b 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.resolve import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useImpl import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.js.naming.NameSuggestion import org.jetbrains.kotlin.js.resolve.diagnostics.* import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap import org.jetbrains.kotlin.resolve.OverloadFilter @@ -56,11 +57,13 @@ object JsPlatformConfigurator : PlatformConfigurator( overridesBackwardCompatibilityHelper = OverridesBackwardCompatibilityHelper.DEFAULT ) { override fun configureModuleComponents(container: StorageComponentContainer) { + container.useInstance(NameSuggestion()) container.useImpl() container.useInstance(SyntheticScopes.Empty) container.useInstance(JsTypeSpecificityComparator) - container.useInstance(JsNameClashChecker()) - container.useInstance(JsNameCharsChecker()) + container.useImpl() + container.useImpl() + container.useImpl() container.useInstance(JsModuleClassLiteralChecker) container.useImpl() container.useImpl() diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index 0b1e1f73106..58b2fb54062 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -44,6 +44,8 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { Renderers.STRING, Renderers.COMPACT) put(ErrorsJs.JS_FAKE_NAME_CLASH, "JavaScript name {0} is generated for different inherited members: {1} and {2}", Renderers.STRING, Renderers.COMPACT, Renderers.COMPACT) + put(ErrorsJs.JS_BUILTIN_NAME_CLASH, "JavaScript name generated for this declaration clashes with built-in declaration {1}", + Renderers.STRING) put(ErrorsJs.JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED, "@JsName annotation is prohibited for primary constructors") put(ErrorsJs.JS_NAME_ON_ACCESSOR_AND_PROPERTY, "@JsName can be either on a property or its accessors, not both of them") put(ErrorsJs.JS_NAME_IS_NOT_ON_ALL_ACCESSORS, "@JsName should be on all of the property accessors") diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 19d593e8afb..e2c05c3ca66 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -48,6 +48,8 @@ public interface ErrorsJs { ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory3 JS_FAKE_NAME_CLASH = DiagnosticFactory3.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory1 JS_BUILTIN_NAME_CLASH = DiagnosticFactory1.create( + ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 JS_NAME_ON_PRIMARY_CONSTRUCTOR_PROHIBITED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_ON_ACCESSOR_AND_PROPERTY = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 JS_NAME_IS_NOT_ON_ALL_ACCESSORS = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt new file mode 100644 index 00000000000..d33c3f36cf2 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsBuiltinNameClashChecker.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.resolve.diagnostics + +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.js.naming.NameSuggestion +import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker + +class JsBuiltinNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker { + override fun check( + declaration: KtDeclaration, descriptor: DeclarationDescriptor, + diagnosticHolder: DiagnosticSink, bindingContext: BindingContext + ) { + if (AnnotationsUtils.isNativeObject(descriptor)) return + if (descriptor.containingDeclaration !is ClassDescriptor) return + + val suggestedName = nameSuggestion.suggest(descriptor)!! + if (!suggestedName.stable) return + val simpleName = suggestedName.names.single() + + if (descriptor is ClassDescriptor) { + if (simpleName in PROHIBITED_STATIC_NAMES) { + diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Function.$simpleName")) + } + } + else if (descriptor is CallableMemberDescriptor) { + if (simpleName in PROHIBITED_MEMBER_NAMES) { + diagnosticHolder.report(ErrorsJs.JS_BUILTIN_NAME_CLASH.on(declaration, "Object.prototype.$simpleName")) + } + } + } + + companion object { + @JvmField + val PROHIBITED_STATIC_NAMES = setOf("prototype", "length", "\$metadata\$") + + @JvmField + val PROHIBITED_MEMBER_NAMES = setOf("constructor") + } +} \ No newline at end of file diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt index 2e45bdbdeae..18bdaf41f9c 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameCharsChecker.kt @@ -41,9 +41,7 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker -class JsNameCharsChecker() : SimpleDeclarationChecker { - val suggestion = NameSuggestion() - +class JsNameCharsChecker(private val suggestion: NameSuggestion) : SimpleDeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, diagnosticHolder: DiagnosticSink, bindingContext: BindingContext diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt index 71c48a391d8..c4d8bd15615 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsNameClashChecker.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty import org.jetbrains.kotlin.resolve.scopes.MemberScope -class JsNameClashChecker : SimpleDeclarationChecker { +class JsNameClashChecker(private val nameSuggestion: NameSuggestion) : SimpleDeclarationChecker { companion object { private val COMMON_DIAGNOSTICS = setOf( Errors.REDECLARATION, @@ -38,7 +38,6 @@ class JsNameClashChecker : SimpleDeclarationChecker { Errors.PACKAGE_OR_CLASSIFIER_REDECLARATION) } - private val nameSuggestion = NameSuggestion() private val scopes = mutableMapOf>() private val clashedFakeOverrides = mutableMapOf>() private val clashedDescriptors = mutableSetOf>() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 429f591ce2f..d9f8a42480c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -6331,6 +6331,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/topLevelFunctionAndParameter.kt"); doTest(fileName); } + + @TestMetadata("withBuiltin.kt") + public void testWithBuiltin() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/nameClashes/withBuiltin.kt"); + doTest(fileName); + } } @TestMetadata("js/js.translator/testData/box/native") diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java index 731c86cc657..67253bbe6c1 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/StaticContext.java @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.context; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.hash.LinkedHashMap; import org.jetbrains.annotations.NotNull; @@ -35,6 +36,7 @@ import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.naming.NameSuggestion; import org.jetbrains.kotlin.js.naming.NameSuggestionKt; import org.jetbrains.kotlin.js.naming.SuggestedName; +import org.jetbrains.kotlin.js.resolve.diagnostics.JsBuiltinNameClashChecker; import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver; import org.jetbrains.kotlin.js.translate.context.generator.Generator; import org.jetbrains.kotlin.js.translate.context.generator.Rule; @@ -146,6 +148,11 @@ public final class StaticContext { private final boolean isStdlib; + private static final Set BUILTIN_JS_PROPERTIES = Sets.union( + JsBuiltinNameClashChecker.PROHIBITED_MEMBER_NAMES, + JsBuiltinNameClashChecker.PROHIBITED_STATIC_NAMES + ); + public StaticContext( @NotNull BindingTrace bindingTrace, @NotNull JsConfig config, @@ -615,7 +622,6 @@ public final class StaticContext { } private final class ScopeGenerator extends Generator { - public ScopeGenerator() { Rule generateNewScopesForClassesWithNoAncestors = descriptor -> { if (!(descriptor instanceof ClassDescriptor)) { @@ -623,6 +629,9 @@ public final class StaticContext { } if (getSuperclass((ClassDescriptor) descriptor) == null) { JsFunction function = new JsFunction(new JsRootScope(program), new JsBlock(), descriptor.toString()); + for (String builtinName : BUILTIN_JS_PROPERTIES) { + function.getScope().declareName(builtinName); + } scopeToFunction.put(function.getScope(), function); return function.getScope(); } diff --git a/js/js.translator/testData/box/nameClashes/withBuiltin.kt b/js/js.translator/testData/box/nameClashes/withBuiltin.kt new file mode 100644 index 00000000000..3aa7c039f0a --- /dev/null +++ b/js/js.translator/testData/box/nameClashes/withBuiltin.kt @@ -0,0 +1,19 @@ +// EXPECTED_REACHABLE_NODES: 1135 +open class C { + private fun constructor() = "C.constructor" + + fun f(): String = constructor() +} + +class D : C() + +fun box(): String { + val d = D() + + val x = d.f() + if (x != "C.constructor") return "fail1: $x" + + if (x.asDynamic().constructor === D::class.js) return "fail2" + + return "OK" +} \ No newline at end of file