Allow empty body blocks with comments (#10969)

close #10849

Changelog:
- update: empty body blocks return an `Empty` node resulting in `Nothing`
This commit is contained in:
Dmitry Bushev 2024-09-04 14:51:15 +03:00 committed by GitHub
parent 0543a69594
commit bc3ab2c7e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 74 additions and 11 deletions

View File

@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.enso.compiler.core.ir.Empty; import org.enso.compiler.core.ir.Empty;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Location; import org.enso.compiler.core.ir.Location;
import org.enso.compiler.core.ir.Module; import org.enso.compiler.core.ir.Module;
import org.enso.compiler.core.ir.expression.errors.Syntax; import org.enso.compiler.core.ir.expression.errors.Syntax;
@ -580,6 +581,19 @@ public class ErrorCompilerTest extends CompilerTest {
assertTrue(method.body() instanceof Empty); assertTrue(method.body() instanceof Empty);
} }
@Test
public void testBodyWithComment() throws Exception {
var ir = parse("""
main =
# comment
""");
var method = (Method) ir.bindings().apply(0);
var body = (Expression.Block) method.body();
assertTrue(body.expressions().isEmpty());
assertTrue(body.returnValue() instanceof Empty);
}
@Test @Test
public void exportAllIsNotAllowed() { public void exportAllIsNotAllowed() {
var ir = parse(""" var ir = parse("""

View File

@ -7463,6 +7463,54 @@ class RuntimeServerTest
) )
} }
it should "run methods with empty body ending with comment" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val code =
"""foo =
| # TODO
|
|main =
| foo
|""".stripMargin.linesIterator.mkString("\n")
val mainFile = context.writeMain(code)
// create context
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some(
Api.Response(requestId, Api.CreateContextResponse(contextId))
)
// Set sources for the module
context.send(
Api.Request(requestId, Api.OpenFileRequest(mainFile, code))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)
// push main
context.send(
Api.Request(
requestId,
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(moduleName, moduleName, "main"),
None,
Vector()
)
)
)
)
context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
context.executionComplete(contextId)
)
}
it should "support file edit notification with IdMap" in { it should "support file edit notification with IdMap" in {
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()

View File

@ -1019,20 +1019,21 @@ final class TreeToIr {
} }
last = translateExpression(expr, false); last = translateExpression(expr, false);
} }
// If the block ended in a documentation node without an expression, last may be null; var locationWithANewLine = getIdentifiedLocation(body, 0, 0, null);
// the return value of the block is a doc comment. if (last == null) {
// (This is to match the behavior of AstToIr; after the parser transition, we should probably if (expressions.isEmpty()) {
// ignore the orphaned documentation and return the last actual expression in the block.) last = new Empty(locationWithANewLine, meta());
if (last == null && expressions.size() > 0) { } else {
last = expressions.get(expressions.size() - 1); last = expressions.get(expressions.size() - 1);
expressions.remove(expressions.size() - 1); expressions.remove(expressions.size() - 1);
}
} }
var list = CollectionConverters.asScala(expressions.iterator()).toList(); var list = CollectionConverters.asScala(expressions.iterator()).toList();
var locationWithANewLine = getIdentifiedLocation(body, 0, 0, null); if (last != null
if (last != null && last.location().isDefined() && last.location().isDefined()
&& last.location().get().end() != locationWithANewLine.get().end()) { && last.location().get().end() != locationWithANewLine.get().end()) {
var patched = new Location(last.location().get().start(), var patched =
locationWithANewLine.get().end() - 1); new Location(last.location().get().start(), locationWithANewLine.get().end() - 1);
var id = IdentifiedLocation.create(patched, last.location().get().id()); var id = IdentifiedLocation.create(patched, last.location().get().id());
last = last.setLocation(Option.apply(id)); last = last.setLocation(Option.apply(id));
} }