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 6 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 @@ -20,6 +20,12 @@
[11235]: https://github.com/enso-org/enso/pull/11235
[11255]: https://github.com/enso-org/enso/pull/11255

#### 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
@@ -0,0 +1,99 @@
package org.enso.compiler.pass.desugar;

import java.util.List;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.ir.Empty;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Literal;
import org.enso.compiler.core.ir.MetadataStorage;
import org.enso.compiler.core.ir.Name;
import org.enso.compiler.core.ir.Pattern;
import org.enso.compiler.core.ir.expression.Case;
import org.enso.compiler.core.ir.expression.IfThenElse;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.MiniIRPass;
import org.enso.compiler.pass.MiniPassFactory;
import org.enso.compiler.pass.analyse.DemandAnalysis$;
import scala.Option;
import scala.collection.immutable.Seq;
import scala.jdk.javaapi.CollectionConverters;

public final class IfThenElseToCase implements MiniPassFactory {

public static final IfThenElseToCase INSTANCE = new IfThenElseToCase();

private IfThenElseToCase() {}

@Override
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes = List.of(GenerateMethodBodies$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}

@Override
public Seq<IRProcessingPass> invalidatedPasses() {
List<IRProcessingPass> passes = List.of(DemandAnalysis$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}

@Override
public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) {
var ctx = InlineContext.fromModuleContext(moduleContext);
return new Mini(ctx);
}

@Override
public MiniIRPass createForInlineCompilation(InlineContext inlineContext) {
return new Mini(inlineContext);
}

private static final class Mini extends MiniIRPass {

private final InlineContext ctx;

private Mini(InlineContext ctx) {
this.ctx = ctx;
}

@Override
public Expression transformExpression(Expression ir) {
return switch (ir) {
case IfThenElse expr -> {
var condArg = expr.cond();
var trueAt = expr.trueBranch().identifiedLocation();
var falseAt = orNull(expr.falseBranch().map(b -> b.identifiedLocation()));
var truePattern =
new Pattern.Literal(new Literal.Bool(true, null, meta()), trueAt, meta());
var trueBranch = new Case.Branch(truePattern, expr.trueBranch(), true, trueAt, meta());
var falseCode =
expr.falseBranch().nonEmpty() ? expr.falseBranch().get() : new Empty(null, meta());
var falsePattern = new Pattern.Name(new Name.Blank(null, meta()), falseAt, meta());
var falseBranch = new Case.Branch(falsePattern, falseCode, true, falseAt, meta());
var branches = join(trueBranch, join(falseBranch, nil()));
var caseExpr = new Case.Expr(condArg, branches, false, expr.identifiedLocation(), meta());
yield caseExpr;
}
default -> ir;
};
}

@SuppressWarnings("unchecked")
private static final <T> scala.collection.immutable.List<T> nil() {
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
}

private static final <T> scala.collection.immutable.List<T> join(
T head, scala.collection.immutable.List<T> tail) {
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
}

private static <T> T orNull(Option<T> opt) {
return opt.isEmpty() ? null : opt.get();
}

private static MetadataStorage meta() {
return new MetadataStorage();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Passes(config: CompilerConfig) {
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
IfThenElseToCase.INSTANCE,
ImportSymbolAnalysis,
AmbiguousImportsAnalysis
) ++ (if (config.privateCheckEnabled) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.enso.compiler.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import java.nio.file.Paths;
import java.util.logging.Level;
import org.enso.common.MethodNames;
import org.enso.common.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.io.IOAccess;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class IfThenElseTest {
private static Context ctx;
private static final ByteArrayOutputStream MESSAGES = new ByteArrayOutputStream();

@BeforeClass
public static void initEnsoContext() {
ctx =
Context.newBuilder()
.allowExperimentalOptions(true)
.allowIO(IOAccess.ALL)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.option(RuntimeOptions.STRICT_ERRORS, "true")
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.logHandler(System.err)
.out(MESSAGES)
.err(MESSAGES)
.allowAllAccess(true)
.build();
assertNotNull("Enso language is supported", ctx.getEngine().getLanguages().get("enso"));
}

@After
public void cleanMessages() {
MESSAGES.reset();
}

@AfterClass
public static void closeEnsoContext() {
ctx.close();
ctx = null;
}

@Test
public void simpleIfThenElse() throws Exception {
var module = ctx.eval("enso", """
check x = if x then "Yes" else "No"
""");

var check = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "check");

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

@Test
public void simpleIfThen() throws Exception {
var module = ctx.eval("enso", """
check x = if x then "Yes"
""");

var check = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "check");

assertEquals("Yes", check.execute(true).asString());
assertTrue("Expect Nothing", check.execute(false).isNull());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.enso.compiler.core.ir.expression.Case;
import org.enso.compiler.core.ir.expression.Comment;
import org.enso.compiler.core.ir.expression.Foreign;
import org.enso.compiler.core.ir.expression.IfThenElse;
import org.enso.compiler.core.ir.expression.Operator;
import org.enso.compiler.core.ir.expression.Section;
import org.enso.compiler.core.ir.expression.errors.Syntax;
Expand Down Expand Up @@ -956,6 +957,20 @@ yield translateSyntaxError(l.getExpression().getExpression(),
sep = "_";
}
var fullName = fnName.toString();
if ("if_then_else".equals(fullName) && args.size() == 3) {
var ifArg = args.apply(2);
var trueArg = args.apply(1);
var falseArg = args.apply(0);
if (falseArg == null) {
yield translateSyntaxError(app, new Syntax.UnsupportedSyntax("Missing else branch"));
}
yield new IfThenElse(ifArg.value(), trueArg.value(), falseArg.value(), getIdentifiedLocation(tree), meta());
}
if ("if_then".equals(fullName) && args.size() == 2) {
var ifArg = args.apply(1);
var trueArg = args.apply(0);
yield new IfThenElse(ifArg.value(), trueArg.value(), null, getIdentifiedLocation(tree), meta());
}
if (fullName.equals(FREEZE_MACRO_IDENTIFIER)) {
yield translateExpression(app.getSegments().get(0).getBody(), false);
} else if (fullName.equals(SKIP_MACRO_IDENTIFIER)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@
@Persistable(clazz = Name.Self.class, id = 705)
@Persistable(clazz = Literal.Number.class, id = 706)
@Persistable(clazz = Literal.Text.class, id = 707)
@Persistable(clazz = CallArgument.Specified.class, id = 708)
@Persistable(clazz = Definition.Type.class, id = 709)
@Persistable(clazz = Definition.Data.class, id = 710)
@Persistable(clazz = Name.Blank.class, id = 711)
@Persistable(clazz = Name.GenericAnnotation.class, id = 712)
@Persistable(clazz = Name.SelfType.class, id = 713)
@Persistable(clazz = Literal.Bool.class, id = 708)
@Persistable(clazz = CallArgument.Specified.class, id = 711)
@Persistable(clazz = Definition.Type.class, id = 712)
@Persistable(clazz = Definition.Data.class, id = 713)
@Persistable(clazz = Name.Blank.class, id = 714)
@Persistable(clazz = Empty.class, id = 715)
@Persistable(clazz = Name.GenericAnnotation.class, id = 716)
@Persistable(clazz = Name.SelfType.class, id = 717)
@Persistable(clazz = Expression.Block.class, id = 751)
@Persistable(clazz = Expression.Binding.class, id = 752)
@Persistable(clazz = Application.Prefix.class, id = 753)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,91 @@ object Literal {
/** @inheritdoc */
override def showCode(indent: Int): String = s""""$text""""
}

/** A boolean Enso literal.
*
* @param bool the value of the literal
* @param identifiedLocation the source location that the node corresponds to
* @param passData the pass metadata associated with this node
*/
sealed case class Bool(
bool: Boolean,
override val identifiedLocation: IdentifiedLocation,
override val passData: MetadataStorage = new MetadataStorage()
) extends Literal
with LazyDiagnosticStorage
with LazyId {

/** Creates a copy of `this`.
*
* @param bool the boolean value of the literal
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
* @param id the identifier for the new node
* @return a copy of `this`, updated with the specified values
*/
def copy(
bool: Boolean = bool,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: UUID @Identifier = id
): Bool = {
if (
bool != this.bool
|| location != this.location
|| passData != this.passData
|| diagnostics != this.diagnostics
|| id != this.id
) {
val res = Bool(bool, location.orNull, passData)
res.diagnostics = diagnostics
res.id = id
res
} else this
}

/** @inheritdoc */
override def duplicate(
keepLocations: Boolean = true,
keepMetadata: Boolean = true,
keepDiagnostics: Boolean = true,
keepIdentifiers: Boolean = false
): Bool =
copy(
location = if (keepLocations) location else None,
passData =
if (keepMetadata) passData.duplicate else new MetadataStorage(),
diagnostics = if (keepDiagnostics) diagnosticsCopy else null,
id = if (keepIdentifiers) id else null
)

/** @inheritdoc */
override def setLocation(location: Option[IdentifiedLocation]): Bool =
copy(location = location)

/** @inheritdoc */
override def mapExpressions(
fn: java.util.function.Function[Expression, Expression]
): Bool = this

/** String representation. */
override def toString: String =
s"""
|Literal.Bool(
|bool = $bool,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".toSingleLine

/** @inheritdoc */
override def children: List[IR] = List()

/** @inheritdoc */
override def showCode(indent: Int): String = s"$bool"
}
}
Loading
Loading