KT-962: Function is called two times
safecall expression now compute receiver only once.
This commit is contained in:
@@ -309,6 +309,28 @@ public class AstUtil {
|
|||||||
throw new AssertionError("Set qualifier should be applied only to JsInvocation or JsNameRef instances");
|
throw new AssertionError("Set qualifier should be applied only to JsInvocation or JsNameRef instances");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JsExpression getQualifier(JsExpression expression) {
|
||||||
|
if (expression instanceof JsInvocation) {
|
||||||
|
return ((JsInvocation) expression).getQualifier();
|
||||||
|
}
|
||||||
|
if (expression instanceof JsNameRef) {
|
||||||
|
return ((JsNameRef) expression).getQualifier();
|
||||||
|
}
|
||||||
|
throw new AssertionError("Get qualifier should be applied only to JsInvocation or JsNameRef instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void substituteTopLevelQualifier(JsExpression expression, JsExpression substitutor) {
|
||||||
|
assert expression != null;
|
||||||
|
JsExpression previous = expression;
|
||||||
|
JsExpression current = getQualifier(expression);
|
||||||
|
assert current != null : "Cannot be applied to unqualified expression";
|
||||||
|
while (getQualifier(current) != null) {
|
||||||
|
previous = current;
|
||||||
|
current = getQualifier(current);
|
||||||
|
}
|
||||||
|
setQualifier(previous, substitutor);
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: look for should-be-usages
|
//TODO: look for should-be-usages
|
||||||
public static JsNameRef qualified(JsName selector, JsExpression qualifier) {
|
public static JsNameRef qualified(JsName selector, JsExpression qualifier) {
|
||||||
JsNameRef reference = selector.makeRef();
|
JsNameRef reference = selector.makeRef();
|
||||||
|
|||||||
@@ -5,54 +5,57 @@ import com.google.dart.compiler.util.AstUtil;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
|
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
|
||||||
|
|
||||||
|
//TODO: track that temporary declarations is in the scope of the block where they are declared
|
||||||
|
|
||||||
public class DynamicContext {
|
public class DynamicContext {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static DynamicContext rootContext(@NotNull NamingScope rootScope, @NotNull JsBlock globalBlock) {
|
public static DynamicContext rootContext(@NotNull NamingScope rootScope, @NotNull JsBlock globalBlock) {
|
||||||
return new DynamicContext(rootScope, globalBlock);
|
return new DynamicContext(rootScope, rootScope, globalBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static DynamicContext contextWithScope(@NotNull NamingScope scope) {
|
public static DynamicContext contextWithScope(@NotNull NamingScope scope) {
|
||||||
return new DynamicContext(scope, new JsBlock());
|
return new DynamicContext(scope, scope, new JsBlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private NamingScope namingScope;
|
private NamingScope currentScope;
|
||||||
|
@NotNull
|
||||||
|
private NamingScope blockScope;
|
||||||
@NotNull
|
@NotNull
|
||||||
private JsBlock currentBlock;
|
private JsBlock currentBlock;
|
||||||
|
|
||||||
private DynamicContext(@NotNull NamingScope scope, @NotNull JsBlock block) {
|
private DynamicContext(@NotNull NamingScope scope, @NotNull NamingScope blockScope, @NotNull JsBlock block) {
|
||||||
this.namingScope = scope;
|
this.currentScope = scope;
|
||||||
this.currentBlock = block;
|
this.currentBlock = block;
|
||||||
|
this.blockScope = blockScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public DynamicContext innerScope(@NotNull JsScope scope) {
|
public DynamicContext innerScope(@NotNull JsScope scope) {
|
||||||
return new DynamicContext(namingScope.innerScope(scope), currentBlock);
|
return new DynamicContext(currentScope.innerScope(scope), blockScope, currentBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public DynamicContext innerBlock(@NotNull JsBlock block) {
|
public DynamicContext innerBlock(@NotNull JsBlock block) {
|
||||||
return new DynamicContext(namingScope, block);
|
return new DynamicContext(currentScope, currentScope, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsName getLocalName(@NotNull DeclarationDescriptor descriptor) {
|
public JsName getLocalName(@NotNull DeclarationDescriptor descriptor) {
|
||||||
JsName name = namingScope.getName(descriptor);
|
JsName name = currentScope.getName(descriptor);
|
||||||
assert name != null : descriptor.getName() + " is not declared. Use isDeclared to check.";
|
assert name != null : descriptor.getName() + " is not declared. Use isDeclared to check.";
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeclared(@NotNull DeclarationDescriptor descriptor) {
|
public boolean isDeclared(@NotNull DeclarationDescriptor descriptor) {
|
||||||
return namingScope.isDeclared(descriptor);
|
return currentScope.isDeclared(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public TemporaryVariable declareTemporary(@NotNull JsExpression initExpression) {
|
public TemporaryVariable declareTemporary(@NotNull JsExpression initExpression) {
|
||||||
|
JsName temporaryName = blockScope.declareTemporary();
|
||||||
JsName temporaryName = namingScope.declareTemporary();
|
|
||||||
JsVars temporaryDeclaration = AstUtil.newVar(temporaryName, null);
|
JsVars temporaryDeclaration = AstUtil.newVar(temporaryName, null);
|
||||||
jsBlock().addVarDeclaration(temporaryDeclaration);
|
jsBlock().addVarDeclaration(temporaryDeclaration);
|
||||||
return new TemporaryVariable(temporaryName, initExpression);
|
return new TemporaryVariable(temporaryName, initExpression);
|
||||||
@@ -60,15 +63,14 @@ public class DynamicContext {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsName declareLocalVariable(@NotNull DeclarationDescriptor descriptor) {
|
public JsName declareLocalVariable(@NotNull DeclarationDescriptor descriptor) {
|
||||||
return namingScope.declareVariable(descriptor, descriptor.getName());
|
return currentScope.declareVariable(descriptor, descriptor.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsScope jsScope() {
|
public JsScope jsScope() {
|
||||||
return namingScope.jsScope();
|
return currentScope.jsScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsBlock jsBlock() {
|
public JsBlock jsBlock() {
|
||||||
return currentBlock;
|
return currentBlock;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.List;
|
|||||||
* @author Pavel.Talanov
|
* @author Pavel.Talanov
|
||||||
*/
|
*/
|
||||||
//TODO: rework translator to translate everything in the namespace not only in one file
|
//TODO: rework translator to translate everything in the namespace not only in one file
|
||||||
|
// TEST IT
|
||||||
public final class NamespaceTranslator extends AbstractTranslator {
|
public final class NamespaceTranslator extends AbstractTranslator {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -66,8 +67,7 @@ public final class NamespaceTranslator extends AbstractTranslator {
|
|||||||
JsInvocation namespaceDeclaration = namespaceCreateMethodInvocation();
|
JsInvocation namespaceDeclaration = namespaceCreateMethodInvocation();
|
||||||
addMemberDeclarations(namespaceDeclaration);
|
addMemberDeclarations(namespaceDeclaration);
|
||||||
addClassesDeclarations(namespaceDeclaration);
|
addClassesDeclarations(namespaceDeclaration);
|
||||||
return AstUtil.newAssignmentStatement
|
return AstUtil.newVar(namespaceName, namespaceDeclaration);
|
||||||
(namespaceName.makeRef(), namespaceDeclaration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addClassesDeclarations(@NotNull JsInvocation namespaceDeclaration) {
|
private void addClassesDeclarations(@NotNull JsInvocation namespaceDeclaration) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.jetbrains.jet.lang.psi.*;
|
|||||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||||
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
|
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
|
||||||
import org.jetbrains.jet.lang.resolve.constants.NullValue;
|
import org.jetbrains.jet.lang.resolve.constants.NullValue;
|
||||||
|
import org.jetbrains.k2js.translate.context.TemporaryVariable;
|
||||||
import org.jetbrains.k2js.translate.context.TranslationContext;
|
import org.jetbrains.k2js.translate.context.TranslationContext;
|
||||||
import org.jetbrains.k2js.translate.general.Translation;
|
import org.jetbrains.k2js.translate.general.Translation;
|
||||||
import org.jetbrains.k2js.translate.general.TranslatorVisitor;
|
import org.jetbrains.k2js.translate.general.TranslatorVisitor;
|
||||||
@@ -16,7 +17,6 @@ import org.jetbrains.k2js.translate.operation.IncrementTranslator;
|
|||||||
import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
|
import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
|
||||||
import org.jetbrains.k2js.translate.reference.AccessTranslator;
|
import org.jetbrains.k2js.translate.reference.AccessTranslator;
|
||||||
import org.jetbrains.k2js.translate.reference.CallTranslator;
|
import org.jetbrains.k2js.translate.reference.CallTranslator;
|
||||||
import org.jetbrains.k2js.translate.reference.PropertyAccessTranslator;
|
|
||||||
import org.jetbrains.k2js.translate.reference.ReferenceTranslator;
|
import org.jetbrains.k2js.translate.reference.ReferenceTranslator;
|
||||||
import org.jetbrains.k2js.translate.utils.BindingUtils;
|
import org.jetbrains.k2js.translate.utils.BindingUtils;
|
||||||
|
|
||||||
@@ -247,29 +247,23 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
|||||||
if (expression.getSelectorExpression() instanceof JetCallExpression) {
|
if (expression.getSelectorExpression() instanceof JetCallExpression) {
|
||||||
return CallTranslator.translate(expression, context);
|
return CallTranslator.translate(expression, context);
|
||||||
}
|
}
|
||||||
return translateQualifiedExpression(expression, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private JsExpression translateQualifiedExpression(@NotNull JetQualifiedExpression expression,
|
|
||||||
@NotNull TranslationContext context) {
|
|
||||||
if (PropertyAccessTranslator.canBePropertyGetterCall(expression, context)) {
|
|
||||||
return PropertyAccessTranslator.translateAsPropertyGetterCall(expression, context);
|
|
||||||
}
|
|
||||||
return translateAsQualifiedAccess(expression, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private JsExpression translateAsQualifiedAccess(@NotNull JetQualifiedExpression expression,
|
|
||||||
@NotNull TranslationContext context) {
|
|
||||||
JsExpression receiver = translateReceiver(expression, context);
|
JsExpression receiver = translateReceiver(expression, context);
|
||||||
JsExpression selector = translateSelector(expression, context);
|
JsExpression selector = translateSelector(expression, context);
|
||||||
assert (selector instanceof JsNameRef || selector instanceof JsInvocation)
|
return composeQualifiedExpression(receiver, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private JsExpression composeQualifiedExpression(@NotNull JsExpression receiver, @NotNull JsExpression selector) {
|
||||||
|
//TODO: make sure that logic would not break for binary operation. check if there is a way to provide clearer logic
|
||||||
|
assert (selector instanceof JsNameRef || selector instanceof JsInvocation || selector instanceof JsBinaryOperation)
|
||||||
: "Selector should be a name reference or a method invocation in dot qualified expression.";
|
: "Selector should be a name reference or a method invocation in dot qualified expression.";
|
||||||
if (selector instanceof JsInvocation) {
|
if (selector instanceof JsInvocation) {
|
||||||
return translateAsQualifiedInvocation(receiver, (JsInvocation) selector);
|
return translateAsQualifiedInvocation(receiver, (JsInvocation) selector);
|
||||||
} else {
|
} else if (selector instanceof JsNameRef) {
|
||||||
return translateAsQualifiedNameReference(receiver, (JsNameRef) selector);
|
return translateAsQualifiedNameReference(receiver, (JsNameRef) selector);
|
||||||
|
} else {
|
||||||
|
((JsBinaryOperation) selector).setArg1(receiver);
|
||||||
|
return selector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,11 +321,13 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
|
public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
|
||||||
@NotNull TranslationContext context) {
|
@NotNull TranslationContext context) {
|
||||||
JsExpression receiver = translateReceiver(expression, context);
|
TemporaryVariable receiver = context.declareTemporary(translateReceiver(expression, context));
|
||||||
JsNullLiteral nullLiteral = context.program().getNullLiteral();
|
JsNullLiteral nullLiteral = context.program().getNullLiteral();
|
||||||
JsExpression thenExpression = translateQualifiedExpression(expression, context);
|
JsExpression selector = translateSelector(expression, context);
|
||||||
return new JsConditional(notNullCheck(context, receiver),
|
JsExpression thenExpression = composeQualifiedExpression(receiver.nameReference(), selector);
|
||||||
thenExpression, nullLiteral);
|
JsConditional callMethodIfNotNullConditional
|
||||||
|
= new JsConditional(notNullCheck(context, receiver.nameReference()), thenExpression, nullLiteral);
|
||||||
|
return AstUtil.newSequence(receiver.assignmentExpression(), callMethodIfNotNullConditional);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public final class ForTranslator extends AbstractTranslator {
|
|||||||
this.expression = forExpression;
|
this.expression = forExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private JsBlock translate() {
|
private JsBlock translate() {
|
||||||
JsName parameterName = declareParameter();
|
JsName parameterName = declareParameter();
|
||||||
@@ -55,9 +54,13 @@ public final class ForTranslator extends AbstractTranslator {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private JsBlock generateCycleBody(@NotNull JsName parameterName, @NotNull TemporaryVariable iterator) {
|
private JsBlock generateCycleBody(@NotNull JsName parameterName, @NotNull TemporaryVariable iterator) {
|
||||||
JsStatement parameterAssignment = AstUtil.newAssignmentStatement(parameterName.makeRef(), nextMethodInvocation(iterator));
|
JsBlock cycleBody = new JsBlock();
|
||||||
JsNode originalBody = Translation.translateExpression(getLoopBody(expression), context());
|
JsStatement parameterAssignment =
|
||||||
return AstUtil.newBlock(parameterAssignment, AstUtil.convertToBlock(originalBody));
|
AstUtil.newVar(parameterName, nextMethodInvocation(iterator));
|
||||||
|
JsNode originalBody = Translation.translateExpression(getLoopBody(expression), context().innerBlock(cycleBody));
|
||||||
|
cycleBody.addStatement(parameterAssignment);
|
||||||
|
cycleBody.addStatement(AstUtil.convertToBlock(originalBody));
|
||||||
|
return cycleBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -26,4 +26,16 @@ public class MiscTest extends AbstractExpressionTest {
|
|||||||
// checkOutput("intRange.kt", " ");
|
// checkOutput("intRange.kt", " ");
|
||||||
testFooBoxIsTrue("intRange.kt");
|
testFooBoxIsTrue("intRange.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void safecallComputesExpressionOnlyOnce() throws Exception {
|
||||||
|
testFooBoxIsTrue("safecallComputesExpressionOnlyOnce.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void KT962() throws Exception {
|
||||||
|
checkOutput("KT-962.kt", "stdout\n" +
|
||||||
|
"Hello, world!\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ fun main(args:Array<String>)
|
|||||||
v.view(it, {(itemLifetime, item)->
|
v.view(it, {(itemLifetime, item)->
|
||||||
x.add(item)
|
x.add(item)
|
||||||
Dump(x)
|
Dump(x)
|
||||||
itemLifetime.attach { x.remove(item); Dump(x) }
|
itemLifetime.attach { x.remove(item as Any); Dump(x) }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fun stdout(): java.io.PrintStream? {
|
||||||
|
System.out?.println("stdout")
|
||||||
|
return System.out
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args : Array<String>) {
|
||||||
|
stdout()?.println("Hello, world!")
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
fun test() : Int? = i++
|
||||||
|
|
||||||
|
fun box() : Boolean {
|
||||||
|
if (i != 0) return false
|
||||||
|
test()?.plus(1)
|
||||||
|
if (i != 1) return false
|
||||||
|
test()?.minus(2)
|
||||||
|
if (i != 2) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user