From 53bbf20b0d5ac278ca916677d616e21746ffa93b Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Thu, 27 Nov 2014 09:19:50 +0300 Subject: [PATCH] Allow augmented assignments on dynamic receivers --- .../resolve/calls/tasks/TaskPrioritizer.java | 11 ++++---- .../lang/resolve/calls/tasks/dynamicCalls.kt | 26 +++++++++++++++---- .../ExpressionTypingVisitorForStatements.java | 3 ++- .../tests/dynamicTypes/conventions.kt | 26 ++++++++++++------- spec-docs/dynamic-types.md | 2 ++ 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java index 0b056534c4f..b266191f48e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/TaskPrioritizer.java @@ -147,9 +147,12 @@ public class TaskPrioritizer { ) { addMembers(explicitReceiver, c, /*static=*/false, isExplicit); - addCandidatesForDynamicReceiver(explicitReceiver, implicitReceivers, c, isExplicit); - - addExtensionCandidates(explicitReceiver, implicitReceivers, c, isExplicit); + if (TypesPackage.isDynamic(explicitReceiver.getType())) { + addCandidatesForDynamicReceiver(explicitReceiver, implicitReceivers, c, isExplicit); + } + else { + addExtensionCandidates(explicitReceiver, implicitReceivers, c, isExplicit); + } } private static void addExtensionCandidates( @@ -208,8 +211,6 @@ public class TaskPrioritizer { @NotNull final TaskPrioritizerContext c, boolean isExplicit ) { - if (!TypesPackage.isDynamic(explicitReceiver.getType())) return; - TaskPrioritizerContext onlyDynamicReceivers = c.replaceCollectors(TasksPackage.onlyDynamicReceivers(c.callableDescriptorCollectors)); addExtensionCandidates(explicitReceiver, implicitReceivers, onlyDynamicReceivers, isExplicit); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt index adfdb0616cc..eafef2bcfeb 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/calls/tasks/dynamicCalls.kt @@ -18,7 +18,6 @@ package org.jetbrains.jet.lang.resolve.calls.tasks import org.jetbrains.jet.lang.psi.Call import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor -import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import org.jetbrains.jet.lang.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.jet.lang.descriptors.annotations.Annotations import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor @@ -43,14 +42,13 @@ import org.jetbrains.jet.lang.resolve.calls.tasks.collectors.CallableDescriptorC import org.jetbrains.jet.lang.resolve.scopes.JetScope import org.jetbrains.jet.lang.resolve.BindingTrace import org.jetbrains.jet.lang.resolve.calls.tasks.collectors.CallableDescriptorCollectors -import org.jetbrains.jet.lexer.JetToken -import org.jetbrains.jet.lang.psi.JetOperationReferenceExpression -import org.jetbrains.jet.lang.psi.JetArrayAccessExpression -import java.util.ArrayList import org.jetbrains.jet.lang.resolve.scopes.JetScopeImpl import org.jetbrains.jet.utils.Printer import org.jetbrains.jet.lang.descriptors.FunctionDescriptor import org.jetbrains.jet.lang.descriptors.VariableDescriptor +import org.jetbrains.jet.lexer.JetTokens +import org.jetbrains.jet.lang.types.expressions.OperatorConventions +import org.jetbrains.jet.lang.psi.JetOperationReferenceExpression object DynamicCallableDescriptors { @@ -62,9 +60,27 @@ object DynamicCallableDescriptors { } override fun getFunctions(name: Name): Collection { + if (isAugmentedAssignmentConvention(name)) return listOf() return listOf(createDynamicFunction(owner, name, call)) } + /* + * Detects the case when name "plusAssign" is requested for "+=" call, + * since both "plus" and "plusAssign" are resolvable on dynamic receivers, + * we have to prefer ne of them, and prefer "plusAssign" for generality: + * it may be called even on a val + */ + private fun isAugmentedAssignmentConvention(name: Name): Boolean { + val callee = call.getCalleeExpression() + if (callee is JetOperationReferenceExpression) { + val token = callee.getReferencedNameElementType() + if (token in JetTokens.AUGMENTED_ASSIGNMENTS && OperatorConventions.ASSIGNMENT_OPERATIONS[token] != name) { + return true + } + } + return false + } + override fun getProperties(name: Name): Collection { return if (call.getValueArgumentList() == null && call.getValueArguments().isEmpty()) { listOf(createDynamicProperty(owner, name, call)) diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingVisitorForStatements.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingVisitorForStatements.java index 9d40483cb1a..2371d840695 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingVisitorForStatements.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/expressions/ExpressionTypingVisitorForStatements.java @@ -36,6 +36,7 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.JetTypeInfo; import org.jetbrains.jet.lang.types.TypeUtils; +import org.jetbrains.jet.lang.types.checker.JetTypeChecker; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.lexer.JetTokens; @@ -284,7 +285,7 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito else if (assignmentOperationType != null && (assignmentOperationDescriptors.isSuccess() || !binaryOperationDescriptors.isSuccess())) { // There's 'plusAssign()', so we do a.plusAssign(b) temporaryForAssignmentOperation.commit(); - if (!KotlinBuiltIns.getInstance().isUnit(assignmentOperationType)) { + if (!JetTypeChecker.DEFAULT.equalTypes(KotlinBuiltIns.getInstance().getUnitType(), assignmentOperationType)) { context.trace.report(ASSIGNMENT_OPERATOR_SHOULD_RETURN_UNIT.on(operationSign, assignmentOperationDescriptors.getResultingDescriptor(), operationSign)); } } diff --git a/compiler/testData/diagnostics/tests/dynamicTypes/conventions.kt b/compiler/testData/diagnostics/tests/dynamicTypes/conventions.kt index 2af34aa882c..00efbce417b 100644 --- a/compiler/testData/diagnostics/tests/dynamicTypes/conventions.kt +++ b/compiler/testData/diagnostics/tests/dynamicTypes/conventions.kt @@ -72,17 +72,23 @@ fun test(d: dynamic) { dVar-- --dVar -// dVar += 1 -// dVar -= 1 -// dVar *= 1 -// dVar /= 1 -// dVar %= 1 + dVar += 1 + dVar -= 1 + dVar *= 1 + dVar /= 1 + dVar %= 1 -// d[1] += 1 -// d[1] -= 1 -// d[1] *= 1 -// d[1] /= 1 -// d[1] %= 1 + d += 1 + d -= 1 + d *= 1 + d /= 1 + d %= 1 + + d[1] += 1 + d[1] -= 1 + d[1] *= 1 + d[1] /= 1 + d[1] %= 1 } val dyn: dynamic = null diff --git a/spec-docs/dynamic-types.md b/spec-docs/dynamic-types.md index 12caa5e3604..04b4575f1e3 100644 --- a/spec-docs/dynamic-types.md +++ b/spec-docs/dynamic-types.md @@ -70,6 +70,8 @@ Internally, `dynamic` is represented as a flexible type `Nothing..Any?`, with th the code may change its semantics just because somebody added some extension in another file. - This means that an extension to a normal, non-dynamic type **can not** be called on a `dynamic` receiver. If needed, one can force a call to an extension by casting the receiver to a static type: `(d as Foo).bar()` +- Augmented assignments on dynamic receivers (e.g. `dyn += foo`) are resolved to `plusAssign()` function, not `plus`, for generality: + this permits calling them on vals (e.g. those holding collection-like objects) ## Type Argument Inference