diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java index 52b22dbcb3c..fc73f338479 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/Errors.java @@ -375,6 +375,9 @@ public interface Errors { } }; + FunctionSignatureDiagnosticFactory CONFLICTING_OVERLOADS = new FunctionSignatureDiagnosticFactory(ERROR, "{1} is already defined in ''{0}''"); + + ParameterizedDiagnosticFactory3 RESULT_TYPE_MISMATCH = ParameterizedDiagnosticFactory3.create(ERROR, "{0} must return {1} but returns {2}"); ParameterizedDiagnosticFactory3 UNSAFE_INFIX_CALL = ParameterizedDiagnosticFactory3.create(ERROR, "Infix call corresponds to a dot-qualified call ''{0}.{1}({2})'' which is not allowed on a nullable receiver ''{0}''. Use '?.'-qualified call instead"); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/FunctionSignatureDiagnosticFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/FunctionSignatureDiagnosticFactory.java new file mode 100644 index 00000000000..4f5e64a5288 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/diagnostics/FunctionSignatureDiagnosticFactory.java @@ -0,0 +1,33 @@ +package org.jetbrains.jet.lang.diagnostics; + +import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.psi.JetClassOrObject; +import org.jetbrains.jet.lang.psi.JetNamedFunction; +import org.jetbrains.jet.resolve.DescriptorRenderer; + +/** + * @author Stepan Koltsov + */ +public class FunctionSignatureDiagnosticFactory extends DiagnosticFactoryWithMessageFormat { + + public FunctionSignatureDiagnosticFactory(Severity severity, String messageTemplate) { + super(severity, messageTemplate); + } + + @NotNull + public Diagnostic on(@NotNull JetNamedFunction functionElement, @NotNull FunctionDescriptor functionDescriptor, + @NotNull String functionContainer) + { + TextRange rangeToMark = new TextRange( + functionElement.getStartOfSignatureElement().getTextRange().getStartOffset(), + functionElement.getEndOfSignatureElement().getTextRange().getEndOffset() + ); + String message = messageFormat.format(new Object[]{ + functionContainer, + DescriptorRenderer.TEXT.render(functionDescriptor)}); + return new GenericDiagnostic(this, severity, message, functionElement.getContainingFile(), rangeToMark); + } + +} //~ diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedFunction.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedFunction.java index 34e96ce83b9..30ffa3b5248 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedFunction.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedFunction.java @@ -40,4 +40,26 @@ public class JetNamedFunction extends JetFunction { return findChildByType(JetTokens.EQ) == null; } + @NotNull + public JetElement getStartOfSignatureElement() { + return this; + } + + @NotNull + public JetElement getEndOfSignatureElement() { + JetElement r = getReturnTypeRef(); + if (r != null) { + return r; + } + + r = getValueParameterList(); + if (r != null) { + return r; + } + + // otherwise it is an error + + return this; + } + } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadResolver.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadResolver.java new file mode 100644 index 00000000000..de621ebf5d0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadResolver.java @@ -0,0 +1,116 @@ +package org.jetbrains.jet.lang.resolve; + +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.*; +import org.jetbrains.jet.lang.diagnostics.Errors; +import org.jetbrains.jet.lang.psi.*; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.jetbrains.jet.lang.resolve.BindingContext.DELEGATED; + +/** + * @author Stepan Koltsov + */ +public class OverloadResolver { + private final TopDownAnalysisContext context; + + public OverloadResolver(@NotNull TopDownAnalysisContext context) { + this.context = context; + } + + public void process() { + checkOverloads(); + } + + private void checkOverloads() { + for (Map.Entry entry : context.getClasses().entrySet()) { + checkOverloadsInAClass(entry.getValue(), entry.getKey()); + } + for (Map.Entry entry : context.getObjects().entrySet()) { + checkOverloadsInAClass(entry.getValue(), entry.getKey()); + } + checkOverloadsInANamespace(); + } + + private void checkOverloadsInANamespace() { + class Key extends Pair { + Key(String namespace, String name) { + super(namespace, name); + } + + public String getNamespace() { + return first; + } + + public String getFunctionName() { + return second; + } + } + + MultiMap functionsByName = MultiMap.create(); + + for (FunctionDescriptorImpl function : context.getFunctions().values()) { + DeclarationDescriptor containingDeclaration = function.getContainingDeclaration(); + if (containingDeclaration instanceof NamespaceDescriptor) { + NamespaceDescriptor namespaceDescriptor = (NamespaceDescriptor) containingDeclaration; + functionsByName.putValue(new Key(namespaceDescriptor.getName(), function.getName()), function); + } + } + + for (Map.Entry> e : functionsByName.entrySet()) { + checkOverloadsWithSameName(e.getKey().getFunctionName(), e.getValue(), e.getKey().getNamespace()); + } + } + + private void checkOverloadsInAClass(MutableClassDescriptor classDescriptor, JetClassOrObject klass) { + MultiMap functionsByName = MultiMap.create(); + + for (FunctionDescriptor function : classDescriptor.getFunctions()) { + functionsByName.putValue(function.getName(), function); + } + + for (Map.Entry> e : functionsByName.entrySet()) { + checkOverloadsWithSameName(e.getKey(), e.getValue(), klass.getName()); + } + + // properties are checked elsewhere + + // Kotlin has no secondary constructors at this time + + } + + private void checkOverloadsWithSameName(String name, Collection functions, String functionContainer) { + if (functions.size() == 1) { + // microoptimization + return; + } + + for (FunctionDescriptor function : functions) { + for (FunctionDescriptor function2 : functions) { + if (function == function2) { + continue; + } + + OverloadUtil.OverloadCompatibilityInfo overloadble = OverloadUtil.isOverloadble(function, function2); + if (!overloadble.isSuccess()) { + JetNamedFunction member = (JetNamedFunction) context.getTrace().get(BindingContext.DESCRIPTOR_TO_DECLARATION, function); + if (member == null) { + assert context.getTrace().get(DELEGATED, function); + return; + } + + context.getTrace().report(Errors.CONFLICTING_OVERLOADS.on(member, function, functionContainer)); + } + } + } + } + +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadUtil.java new file mode 100644 index 00000000000..c33d452ada0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverloadUtil.java @@ -0,0 +1,52 @@ +package org.jetbrains.jet.lang.resolve; + +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; + +/** + * @author Stepan Koltsov + */ +public class OverloadUtil { + + public static OverloadCompatibilityInfo isOverloadble(FunctionDescriptor a, FunctionDescriptor b) { + OverridingUtil.OverrideCompatibilityInfo overrideCompatibilityInfo = OverridingUtil.isOverridableByImpl(a, b, false); + if (overrideCompatibilityInfo.isSuccess()) { + return OverloadCompatibilityInfo.someError(); + } else { + return OverloadCompatibilityInfo.success(); + } + } + + public static class OverloadCompatibilityInfo { + + private static final OverloadCompatibilityInfo SUCCESS = new OverloadCompatibilityInfo(true, "SUCCESS"); + + public static OverloadCompatibilityInfo success() { + return SUCCESS; + } + + public static OverloadCompatibilityInfo someError() { + return new OverloadCompatibilityInfo(false, "XXX"); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private final boolean isSuccess; + private final String message; + + public OverloadCompatibilityInfo(boolean success, String message) { + isSuccess = success; + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public String getMessage() { + return message; + } + + } + +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverridingUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverridingUtil.java index 53b95a441f4..6b26f59f733 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverridingUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/OverridingUtil.java @@ -93,6 +93,13 @@ public class OverridingUtil { @NotNull public static OverrideCompatibilityInfo isOverridableBy(@NotNull CallableDescriptor superDescriptor, @NotNull CallableDescriptor subDescriptor) { + return isOverridableByImpl(superDescriptor, subDescriptor, true); + } + + /** + * @param forOverride true for override, false for overload + */ + static OverrideCompatibilityInfo isOverridableByImpl(CallableDescriptor superDescriptor, CallableDescriptor subDescriptor, boolean forOverride) { if (superDescriptor instanceof FunctionDescriptor) { if (subDescriptor instanceof PropertyDescriptor) return OverrideCompatibilityInfo.memberKindMismatch(); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java index 48a8733ee82..eefbe5a7e4b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/TopDownAnalyzer.java @@ -52,6 +52,7 @@ public class TopDownAnalyzer { new DeclarationResolver(context).process(); new DelegationResolver(context).process(); new OverrideResolver(context).process(); + new OverloadResolver(context).process(); new BodyResolver(context).resolveBehaviorDeclarationBodies(); new ControlFlowAnalyzer(context, flowDataTraceFactory, declaredLocally).process(); new DeclarationsChecker(context).process(); diff --git a/compiler/testData/checkerWithErrorTypes/quick/ConflictingOverloads.jet b/compiler/testData/checkerWithErrorTypes/quick/ConflictingOverloads.jet new file mode 100644 index 00000000000..ab9380427fb --- /dev/null +++ b/compiler/testData/checkerWithErrorTypes/quick/ConflictingOverloads.jet @@ -0,0 +1,42 @@ +// http://youtrack.jetbrains.net/issue/KT-424 + +class A { + fun a(a: Int): Int = 0 + + fun a(a: Int) { + } + + fun b() { + } + + fun b() { + } +} + +namespace deepSpace { + fun c(s: String) { + } + + fun c(s: String) { + } + + class B { + fun d(s: String) { + } + + fun d(s: String) { + } + } +} + + +// check no error in overload in different namespaces + +namespace ns1 { + fun e() = 1 +} + +namespace ns2 { + fun e() = 1 +} + diff --git a/compiler/testData/checkerWithErrorTypes/quick/nullabilityAndAutoCasts/ReceiverNullability.jet b/compiler/testData/checkerWithErrorTypes/quick/nullabilityAndAutoCasts/ReceiverNullability.jet index 0995252deff..3bea190f1d3 100644 --- a/compiler/testData/checkerWithErrorTypes/quick/nullabilityAndAutoCasts/ReceiverNullability.jet +++ b/compiler/testData/checkerWithErrorTypes/quick/nullabilityAndAutoCasts/ReceiverNullability.jet @@ -15,7 +15,7 @@ fun test(a : A?) { a?.buzz() // warning } -fun A.test() { +fun A.test2() { foo() bar() buzz() diff --git a/compiler/tests/org/jetbrains/jet/types/JetOverloadTest.java b/compiler/tests/org/jetbrains/jet/types/JetOverloadTest.java new file mode 100644 index 00000000000..30916fa528a --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/types/JetOverloadTest.java @@ -0,0 +1,158 @@ +package org.jetbrains.jet.types; + +import org.jetbrains.jet.JetLiteFixture; +import org.jetbrains.jet.JetTestCaseBuilder; +import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.lang.JetSemanticServices; +import org.jetbrains.jet.lang.descriptors.FunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.ModuleDescriptor; +import org.jetbrains.jet.lang.psi.JetNamedFunction; +import org.jetbrains.jet.lang.psi.JetPsiFactory; +import org.jetbrains.jet.lang.resolve.ClassDescriptorResolver; +import org.jetbrains.jet.lang.resolve.OverloadUtil; +import org.jetbrains.jet.lang.types.JetStandardLibrary; + +/** + * @author Stepan Koltsov + */ +public class JetOverloadTest extends JetLiteFixture { + + private ModuleDescriptor root = new ModuleDescriptor("test_root"); + private JetStandardLibrary library; + private JetSemanticServices semanticServices; + private ClassDescriptorResolver classDescriptorResolver; + + @Override + public void setUp() throws Exception { + super.setUp(); + library = JetStandardLibrary.getJetStandardLibrary(getProject()); + semanticServices = JetSemanticServices.createSemanticServices(library); + classDescriptorResolver = semanticServices.getClassDescriptorResolver(JetTestUtils.DUMMY_TRACE); + } + + @Override + protected String getTestDataPath() { + return JetTestCaseBuilder.getTestDataPathBase(); + } + + public void testBasic() throws Exception { + + assertNotOverloadable( + "fun a() : Int", + "fun a() : Int"); + + assertNotOverloadable( + "fun a() : Int", + "fun a() : Any"); + + assertNotOverloadable( + "fun a() : T1", + "fun a() : T"); + + assertNotOverloadable( + "fun a(a : T1) : T1", + "fun a(a : T) : T"); + + assertNotOverloadable( + "fun a(a : T1) : T1", + "fun a(a : T) : T"); + + assertNotOverloadable( + "fun a(a : T1) : T1", + "fun a(a : T) : Y"); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + assertOverloadable( + "fun ab() : Int", + "fun a() : Int"); + + assertNotOverloadable( + "fun a() : Int", + "fun a() : Any"); + + assertOverloadable( + "fun a(a : Int) : Int", + "fun a() : Int"); + + assertOverloadable( + "fun a() : Int", + "fun a(a : Int) : Int"); + + assertOverloadable( + "fun a(a : Int?) : Int", + "fun a(a : Int) : Int"); + + // XXX: different from overridable + /* + assertNotOverloadable( + "fun a(a : Int) : Int", + "fun a(a : Int) : Int"); + */ + + assertOverloadable( + "fun a(a : T1) : T1", + "fun a(a : T) : T"); + + assertOverloadable( + "fun a(a : T1) : T1", + "fun a(a : Y) : T"); + + assertNotOverloadable( + "fun a(a : T1) : X", + "fun a(a : T) : T"); + + assertNotOverloadable( + "fun a>(a : Array) : T1", + "fun a>(a : Array) : T"); + + assertOverloadable( + "fun a>(a : Array) : T1", + "fun a>(a : Array) : T"); + + assertOverloadable( + "fun a>(a : Array) : T1", + "fun a>(a : Array) : T"); + + assertOverloadable( + "fun a>(a : Array) : T1", + "fun a>(a : Array) : T"); + + assertOverloadable( + "fun a>(a : Array) : T1", + "fun a>(a : Array) : T"); + + assertOverloadable( + "fun a>(a : Array<*>) : T1", + "fun a>(a : Array) : T"); + + } + + private void assertNotOverloadable(String funA, String funB) { + assertOverloadabilityRelation(funA, funB, true); + } + + private void assertOverloadable(String funA, String funB) { + assertOverloadabilityRelation(funA, funB, false); + } + + private void assertOverloadabilityRelation(String funA, String funB, boolean expectedIsError) { + FunctionDescriptor a = makeFunction(funA); + FunctionDescriptor b = makeFunction(funB); + { + OverloadUtil.OverloadCompatibilityInfo overloadableWith = OverloadUtil.isOverloadble(a, b); + assertEquals(overloadableWith.getMessage(), expectedIsError, !overloadableWith.isSuccess()); + } + { + OverloadUtil.OverloadCompatibilityInfo overloadableWith = OverloadUtil.isOverloadble(b, a); + assertEquals(overloadableWith.getMessage(), expectedIsError, !overloadableWith.isSuccess()); + } + } + + private FunctionDescriptor makeFunction(String funDecl) { + JetNamedFunction function = JetPsiFactory.createFunction(getProject(), funDecl); + return classDescriptorResolver.resolveFunctionDescriptor(root, library.getLibraryScope(), function); + } + +} diff --git a/compiler/tests/org/jetbrains/jet/types/JetOverridingTest.java b/compiler/tests/org/jetbrains/jet/types/JetOverridingTest.java index 4de8bd9a285..18b27921f61 100644 --- a/compiler/tests/org/jetbrains/jet/types/JetOverridingTest.java +++ b/compiler/tests/org/jetbrains/jet/types/JetOverridingTest.java @@ -67,6 +67,13 @@ public class JetOverridingTest extends JetLiteFixture { "fun a() : Int", "fun a() : Any"); + // return types are not cheked in the utility + /* + assertNotOverridable( + "fun a() : Any", + "fun a() : Int"); + */ + assertNotOverridable( "fun a(a : Int) : Int", "fun a() : Int");