Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recognize if-like Tree.MultiSegmentApp as IfThenElse IR #11365

Draft
wants to merge 44 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6abf764
Recognize if-like Tree.MultiSegmentApp as IfThenElse IR
JaroslavTulach Oct 19, 2024
f5ca8e0
Converting IfThenElse IR to case statement
JaroslavTulach Oct 20, 2024
f998708
Support if_then without else
JaroslavTulach Oct 20, 2024
fba42c5
Register Literal.Bool and Empty for persistance
JaroslavTulach Oct 20, 2024
7c7c5b3
Note in changelog
JaroslavTulach Oct 20, 2024
94b07b4
Handle missing else branch without NPE
JaroslavTulach Oct 20, 2024
f6f0938
Testing behavior of a variable being defined in branch
JaroslavTulach Oct 24, 2024
3a2906b
Variable cannot be use after the if branch is over
JaroslavTulach Oct 24, 2024
3a62199
Bringing in changes from develop, mainly #11395
JaroslavTulach Oct 25, 2024
ffab938
Removing changes related to IfThenElseToCase IR conversions
JaroslavTulach Oct 25, 2024
382a0df
Processing IfThenElse IR via controlflow.IfThenElseNode
JaroslavTulach Oct 25, 2024
6b6be17
IfThenElse can access local variables
JaroslavTulach Oct 25, 2024
2835bb4
IfThenElse uses addChild(flattenToParent=true) to process, but isolat…
JaroslavTulach Oct 25, 2024
97c90b8
@Specialization methods don't have to be public
JaroslavTulach Oct 25, 2024
3420d33
Using @Specialization to handle various IfThenElseNode situations
JaroslavTulach Oct 25, 2024
063e7f0
Handling Warnings WithCondition node
JaroslavTulach Oct 25, 2024
4f4fa1d
javafmtAll
JaroslavTulach Oct 25, 2024
c2c6b24
Test showing that definition of a variable in if branch overrides global
JaroslavTulach Oct 26, 2024
039ba40
Fixing typo
JaroslavTulach Oct 26, 2024
9f75eb1
No need for boolean literal right now
JaroslavTulach Oct 26, 2024
dbaf5f3
Recalculate offset when flattenToParent
JaroslavTulach Oct 26, 2024
e852d8a
Adjust the test to IfThenElse element
JaroslavTulach Oct 26, 2024
00a69f7
Adjusting the stacktrace: implementation details disappear with IfThe…
JaroslavTulach Oct 26, 2024
c440305
Use if_then function to get mixfix functions semantics
JaroslavTulach Oct 26, 2024
9c747d2
Towards single Scope for all if-then-else branches
JaroslavTulach Oct 28, 2024
546e103
Merging with most recent develop and resolving clashes with #11419
JaroslavTulach Oct 29, 2024
4b9e993
Cannot have argument b and local variable b anymore
JaroslavTulach Oct 29, 2024
1b7ad3c
Give IfThenElseNode a location
JaroslavTulach Oct 29, 2024
35ab5dc
Avoiding t1 and t2 name clash
JaroslavTulach Oct 29, 2024
6ab657d
Merge remote-tracking branch 'origin/develop' into wip/jtulach/IfThen…
JaroslavTulach Nov 1, 2024
0224c00
Merging with GraphBuilder in develop
JaroslavTulach Nov 5, 2024
c065882
Use internal variable name
JaroslavTulach Nov 5, 2024
d36fc2e
Assert currentFrame is not null
JaroslavTulach Nov 6, 2024
bfc4969
Use GraphBuilder more
JaroslavTulach Nov 6, 2024
aa93b06
Expanding AliasAnalysisTest with if/then section
JaroslavTulach Nov 7, 2024
fbe2324
scalafmtAll
JaroslavTulach Nov 7, 2024
9f6d277
Merge remote-tracking branch 'origin/develop' into wip/jtulach/IfThen…
JaroslavTulach Nov 22, 2024
e2a35e1
Merge remote-tracking branch 'origin/develop' into wip/jtulach/IfThen…
JaroslavTulach Dec 14, 2024
933d74c
require non null trueBranch
JaroslavTulach Dec 14, 2024
71cedf2
executeAppend with frame
JaroslavTulach Dec 14, 2024
f2d5574
Note applies to both case and if/then/else
JaroslavTulach Dec 14, 2024
63dde4e
Trying to mimic failure in Index_Sub_Range
JaroslavTulach Dec 14, 2024
46e21af
Associate runModule exceptions with name of the module
JaroslavTulach Dec 14, 2024
41162aa
Sharing of an IR at multiple places is common. Only detect cases when…
JaroslavTulach Dec 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@

[11374]: https://github.com/enso-org/enso/pull/11374

#### Enso Language & Runtime

- [Unify internal handling of if/then/else and case statement][11365]

[11365]: https://github.com/enso-org/enso/pull/11365

# Enso 2024.4

#### Enso IDE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected IgnoredBindings.State readObject(Input in)
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
public static final class PersistAliasAnalysisGraphScope extends Persistance<Graph.Scope> {
public PersistAliasAnalysisGraphScope() {
super(Graph.Scope.class, false, 1267);
super(Graph.Scope.class, false, 1269);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.core.ir.expression.Case;
import org.enso.compiler.core.ir.expression.Comment;
import org.enso.compiler.core.ir.expression.IfThenElse;
import org.enso.compiler.core.ir.module.scope.Definition;
import org.enso.compiler.core.ir.module.scope.definition.*;
import org.enso.compiler.pass.IRPass;
Expand Down Expand Up @@ -206,6 +207,16 @@ public Expression transformExpression(Expression ir) {
// Note [Call Argument Tail Position]
p.arguments().foreach(a -> markAsTail(a));
}
case IfThenElse ite -> {
if (isInTailPos) {
markAsTail(ite);
// Note [Analysing Branches in Case Expressions]
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
markAsTail(ite.trueBranch());
if (ite.falseBranchOrNull() != null) {
markAsTail(ite.falseBranchOrNull());
}
}
}
case Case.Expr e -> {
if (isInTailPos) {
markAsTail(ir);
Expand Down Expand Up @@ -246,6 +257,15 @@ private void collectTailCandidatesExpression(
Expression expression, java.util.Map<IR, Boolean> tailCandidates) {
switch (expression) {
case Function function -> collectTailCandicateFunction(function, tailCandidates);
case IfThenElse ite -> {
if (isInTailPos) {
// Note [Analysing Branches in Case Expressions]
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
tailCandidates.put(ite.trueBranch(), true);
if (ite.falseBranchOrNull() != null) {
tailCandidates.put(ite.falseBranchOrNull(), true);
}
}
}
case Case caseExpr -> collectTailCandidatesCase(caseExpr, tailCandidates);
case Application app -> collectTailCandidatesApplication(app, tailCandidates);
case Name name -> collectTailCandidatesName(name, tailCandidates);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.enso.compiler.core.ir.expression.{
Case,
Comment,
Error,
IfThenElse,
Operator,
Section
}
Expand Down Expand Up @@ -386,7 +387,8 @@ case object AliasAnalysis extends IRPass {
graph,
parentScope
)
case cse: Case => analyseCase(cse, graph, parentScope)
case ife: IfThenElse => analyseIfThenElse(ife, graph, parentScope)
case cse: Case => analyseCase(cse, graph, parentScope)
case block: Expression.Block =>
val currentScope =
if (!block.suspended) parentScope else parentScope.addChild()
Expand Down Expand Up @@ -414,7 +416,7 @@ case object AliasAnalysis extends IRPass {
)
case binding @ Expression.Binding(name, expression, _, _) =>
if (
!parentScope.hasSymbolOccurrenceAs[GraphOccurrence.Def](name.name)
true // !parentScope.hasSymbolOccurrenceAs[GraphOccurrence.Def](name.name)
) {
val isSuspended = expression match {
case Expression.Block(_, _, _, isSuspended, _) => isSuspended
Expand Down Expand Up @@ -448,7 +450,8 @@ case object AliasAnalysis extends IRPass {
)
)
} else {
errors.Redefined.Binding(binding)
// errors.Redefined.Binding(binding)
binding
}
case app: Application =>
analyseApplication(app, graph, parentScope)
Expand Down Expand Up @@ -794,14 +797,37 @@ case object AliasAnalysis extends IRPass {
)
}

/** Performs alias analysis on a if then else expression.
*
* @param ir the expression to analyse
* @param graph the graph in which the analysis is taking place
* @param parentScope the scope in which the expression occurs
* @return `ir`, possibly with alias analysis information attached
*/
private def analyseIfThenElse(
ir: IfThenElse,
graph: Graph,
parentScope: Scope
): IfThenElse = {
val condScope = parentScope; // .addChild()
val trueScope = parentScope; // .addChild()
val falseScope = parentScope; // .addChild()
ir.copy(
cond = analyseExpression(ir.cond, graph, condScope),
trueBranch = analyseExpression(ir.trueBranch, graph, trueScope),
falseBranchOrNull =
ir.falseBranch.map(analyseExpression(_, graph, falseScope)).orNull
)
}

/** Performs alias analysis on a case expression.
*
* @param ir the case expression to analyse
* @param graph the graph in which the analysis is taking place
* @param parentScope the scope in which the case expression occurs
* @return `ir`, possibly with alias analysis information attached
*/
def analyseCase(
private def analyseCase(
ir: Case,
graph: Graph,
parentScope: Scope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.enso.compiler.core.ir.expression.{
Comment,
Error,
Foreign,
IfThenElse,
Operator
}
import org.enso.compiler.core.ir.{
Expand Down Expand Up @@ -244,6 +245,7 @@ case object DataflowAnalysis extends IRPass {
case app: Application => analyseApplication(app, info)
case typ: Type => analyseType(typ, info)
case name: Name => analyseName(name, info)
case ife: IfThenElse => analyseIfThenElse(ife, info)
case cse: Case => analyseCase(cse, info)
case literal: Literal =>
literal.updateMetadata(new MetadataPair(this, info))
Expand Down Expand Up @@ -580,6 +582,31 @@ case object DataflowAnalysis extends IRPass {
}
}

/** Performs dependency analysis on a if then else expression.
*
* The value of a if expression is dependent on both its condition and the
* definitions of its branches. The computation of the branches also depends
* on the condition.
*
* @param ife the expression
* @param info the dependency information for the module
* @return `ife`, with attached dependency information
*/
private def analyseIfThenElse(
ife: IfThenElse,
info: DependencyInfo
): IfThenElse = {
val ifeDep = asStatic(ife)
val condDep = asStatic(ife.cond)
info.dependents.updateAt(condDep, Set(ifeDep))
info.dependencies.updateAt(ifeDep, Set(condDep))
ife
.copy(
cond = analyseExpression(ife.cond, info)
)
.updateMetadata(new MetadataPair(this, info))
}

/** Performs dependency analysis on a case expression.
*
* The value of a case expression is dependent on both its scrutinee and the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.enso.compiler.core.ir.expression.{
Comment,
Error,
Foreign,
IfThenElse,
Operator
}
import org.enso.compiler.core.CompilerError
Expand Down Expand Up @@ -114,6 +115,8 @@ case object DemandAnalysis extends IRPass {
analyseApplication(app, isInsideCallArgument)
case typ: Type =>
analyseType(typ, isInsideCallArgument)
case ite: IfThenElse =>
analyseIfThenElse(ite, isInsideCallArgument)
case cse: Case =>
analyseCase(cse, isInsideCallArgument)
case block @ Expression.Block(expressions, retVal, _, _, _) =>
Expand Down Expand Up @@ -309,6 +312,25 @@ case object DemandAnalysis extends IRPass {
): Type =
typ.mapExpressions(x => analyseExpression(x, isInsideCallArgument))

/** Performs demand analysis on an if/then/else expression.
*
* @param ife the expression
* @param isInsideCallArgument whether expression occurs inside a
* function call argument
* @return `cse`, transformed by the demand analysis process
*/
private def analyseIfThenElse(
ife: IfThenElse,
isInsideCallArgument: Boolean
): IfThenElse = {
ife.copy(
cond = analyseExpression(ife.cond, isInsideCallArgument),
trueBranch = analyseExpression(ife.trueBranch, false),
falseBranchOrNull =
ife.falseBranch.map(analyseExpression(_, false)).orNull
)
}

/** Performs demand analysis on a case expression.
*
* @param cse the case expression to perform demand analysis on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.enso.compiler.pass.analyse.FramePointer
import org.enso.compiler.context.{InlineContext, LocalScope, ModuleContext}
import org.enso.compiler.core.ir.Name.GenericAnnotation
import org.enso.compiler.core.{CompilerError, IR}
import org.enso.compiler.core.ir.expression.{Application, Case}
import org.enso.compiler.core.ir.expression.{Application, Case, IfThenElse}
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
Expand Down Expand Up @@ -159,6 +159,10 @@ case object FramePointerAnalysis extends IRPass {
processExpression(expr, graph)
maybeAttachFramePointer(binding, graph)
case app: Application => processApplication(app, graph)
case ife: IfThenElse =>
processExpression(ife.cond, graph)
processExpression(ife.trueBranch, graph)
ife.falseBranch.map(processExpression(_, graph))
case caseExpr: Case.Expr =>
processExpression(caseExpr.scrutinee, graph)
caseExpr.branches.foreach { branch =>
Expand Down Expand Up @@ -356,7 +360,8 @@ case object FramePointerAnalysis extends IRPass {
"Def occurrence must be in the given scope"
)
)
idxInScope + LocalScope.internalSlotsSize
val parentOffset = 0
parentOffset + idxInScope + LocalScope.internalSlotsSize
}

/** Returns the *scope distance* of the given `childScope` to the given `parentScope`.
Expand All @@ -372,8 +377,8 @@ case object FramePointerAnalysis extends IRPass {
var currScope: Option[Graph.Scope] = Some(childScope)
var scopeDistance = 0
while (currScope.isDefined && currScope.get != parentScope) {
currScope = currScope.get.parent
scopeDistance += 1
currScope = currScope.get.parent
}
scopeDistance
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ sealed class Graph(

/** @return the next counter value
*/
def nextIdCounter: Int = _nextIdCounter
private[compiler] def nextIdCounter: Int = _nextIdCounter

/** @return a deep structural copy of `this` */
final def deepCopy(
Expand Down Expand Up @@ -364,7 +364,11 @@ object Graph {
childScopeCopies += scope.deepCopy(mapping)
)
val newScope =
new Scope(childScopeCopies.toList, occurrences, allDefinitions)
new Scope(
childScopeCopies.toList,
occurrences,
allDefinitions
)
mapping.put(this, newScope)
newScope
}
Expand Down Expand Up @@ -392,6 +396,7 @@ object Graph {

/** Creates and returns a scope that is a child of this one.
*
* @param is this scope "just virtual" and will be flatten to parent at the end?
* @return a scope that is a child of `this`
*/
final def addChild(): Scope = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Value;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.hamcrest.core.AllOf;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

public class IfThenElseTest {
Expand Down Expand Up @@ -96,6 +96,43 @@ public void variableDefinedInElse() throws Exception {
assertEquals("Bad:False", check.execute(false).asString());
}

@Test
public void variableInMultipleIfBranches() throws Exception {
var code =
"""
check x =
if x then
xt = "Yes"
if x.not then
xt = "No"
"Hooo"
""";
var check = ContextUtils.getMethodFromModule(ctx, code, "check");

assertEquals("Hooo", check.execute(true).asString());
assertEquals("Hooo", check.execute(false).asString());
}

@Test
public void variableNotVisibleAfterBranches() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing scopes of if then else is a good idea. I would like to see more expanded tests. Something like:

x = True
if x then
  y = True
  if x then if y then "Foo"

and

x = False
cond = False
if cond then "cond" else
  y = False
  if x then "x" else
    if y then "y" else 42

that is, try to use bindings from various parent scopes in the condition, true branch and false branch.

These tests could be placed in Base_Tests as well I believe.

var code =
"""
check x =
if x then
xt = "Yes"
if x.not then
xt = "No"
xt
""";
try {
var check = ContextUtils.getMethodFromModule(ctx, code, "check");
fail("The code should not compile, but returned: " + check);
} catch (PolyglotException ex) {
MatcherAssert.assertThat(
ex.getMessage(), Matchers.containsString("name `xt` could not be found"));
}
}

@Test
public void variableUsedAfterTheBranch() throws Exception {
try {
Expand Down Expand Up @@ -160,7 +197,6 @@ public void javaScriptBooleanIsSupported() throws Exception {
assertEquals("No", check.execute("Ne").asString());
}

@Ignore
@Test
public void truffleObjectConvertibleToBooleanIsSupported() throws Exception {
var code =
Expand Down Expand Up @@ -296,4 +332,26 @@ private static void assertWarning(String txt, Value v) {
assertEquals(txt, ex.getMessage());
}
}

@Test
public void dontOverrideVariablesFromOuterScope() throws Exception {
var code =
"""
type Hello
World msg good

join self init =
if self.good then "Ciao" else
x = init
x + self.msg

hello state =
Hello.World "World" state . join "Hello "
""";

var hello = ContextUtils.getMethodFromModule(ctx, code, "hello");

assertEquals("Ciao", hello.execute(true).asString());
assertEquals("Hello World", hello.execute(false).asString());
}
}
Loading
Loading