From efde3f0db3bb28aa7479e12b7627ef0545f4c7b5 Mon Sep 17 00:00:00 2001 From: LTeuse Date: Fri, 10 Apr 2026 12:17:59 +0200 Subject: [PATCH 1/4] add a check if we are in hole in Lexer/DepthTracker --- effekt/shared/src/main/scala/effekt/Lexer.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/Lexer.scala b/effekt/shared/src/main/scala/effekt/Lexer.scala index 73ba5828e..6d26db15d 100644 --- a/effekt/shared/src/main/scala/effekt/Lexer.scala +++ b/effekt/shared/src/main/scala/effekt/Lexer.scala @@ -227,7 +227,7 @@ object Position { * interpolation boundaries. When we see ${, we record the current brace depth * and know the interpolation ends when we return to that depth. */ -case class DepthTracker(var parens: Int, var braces: Int, var brackets: Int) +case class DepthTracker(var parens: Int, var braces: Int, var brackets: Int, var holes: Boolean) /** * Never throws exceptions - always returns Error tokens for errors. @@ -246,7 +246,7 @@ class Lexer(source: Source) extends Iterator[Token] { // String interpolation state private val delimiters = mutable.Stack[Delimiter]() - private val depthTracker = DepthTracker(0, 0, 0) + private val depthTracker = DepthTracker(0, 0, 0, false) private val interpolationDepths = mutable.Stack[Int]() /** @@ -444,7 +444,9 @@ class Lexer(source: Source) extends Iterator[Token] { case ('<', '<') => advance2With(TokenKind.`<<`) case ('<', '=') => advance2With(TokenKind.`<=`) case ('<', '>') => advance2With(TokenKind.`<>`) - case ('<', '{') => advance2With(TokenKind.`<{`) + case ('<', '{') => + depthTracker.holes = true + advance2With(TokenKind.`<{`) case ('<', '~') => advance2With(TokenKind.`<~`) case ('<', _) => advanceWith(TokenKind.`<`) @@ -480,7 +482,9 @@ class Lexer(source: Source) extends Iterator[Token] { case ('$', _) => advanceWith(TokenKind.Error(LexerError.UnknownChar('$'))) - case ('}', '>') => advance2With(TokenKind.`}>`) + case ('}', '>') if depthTracker.holes => + depthTracker.holes = false + advance2With(TokenKind.`}>`) case ('}', _) if isAtInterpolationBoundary => interpolationDepths.pop() depthTracker.braces -= 1 From 2c73a136895d73fafccec34e9c94cfe9bcf0d9e0 Mon Sep 17 00:00:00 2001 From: LTeuse Date: Fri, 10 Apr 2026 13:16:39 +0200 Subject: [PATCH 2/4] add holes checks --- .../src/test/scala/effekt/LexerTests.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/effekt/jvm/src/test/scala/effekt/LexerTests.scala b/effekt/jvm/src/test/scala/effekt/LexerTests.scala index 0c335b422..0c70e5781 100644 --- a/effekt/jvm/src/test/scala/effekt/LexerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/LexerTests.scala @@ -240,6 +240,33 @@ class LexerTests extends munit.FunSuite { ) } + test("string splice hole") { + val prog = "<${x}>" + assertTokensEq( + prog, + `<`, `${`, Ident("x"), `}$`, `>`, + EOF + ) + } + + test("all holes") { + val prog = "<{<\"str\">; <>}>" + assertTokensEq( + prog, + `<{`, HoleStr("str"), `;`, `<>`, `}>`, + EOF + ) + } + + test("all holes imbriqued") { + val prog = "<{< <\"str\"> >}>" + assertTokensEq( + prog, + `<{`, `<`, HoleStr("str"), `>`, `}>`, + EOF + ) + } + test("multiline string holes") { val prog1: String = """<" Here it starts From 608f702e2de9026c3f41b0f257e29e31d372dbc8 Mon Sep 17 00:00:00 2001 From: LTeuse Date: Fri, 10 Apr 2026 13:48:38 +0200 Subject: [PATCH 3/4] add nested block hole --- effekt/shared/src/main/scala/effekt/Lexer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/Lexer.scala b/effekt/shared/src/main/scala/effekt/Lexer.scala index 6d26db15d..5dc6ea945 100644 --- a/effekt/shared/src/main/scala/effekt/Lexer.scala +++ b/effekt/shared/src/main/scala/effekt/Lexer.scala @@ -227,7 +227,7 @@ object Position { * interpolation boundaries. When we see ${, we record the current brace depth * and know the interpolation ends when we return to that depth. */ -case class DepthTracker(var parens: Int, var braces: Int, var brackets: Int, var holes: Boolean) +case class DepthTracker(var parens: Int, var braces: Int, var brackets: Int, var holes: Int) /** * Never throws exceptions - always returns Error tokens for errors. @@ -246,7 +246,7 @@ class Lexer(source: Source) extends Iterator[Token] { // String interpolation state private val delimiters = mutable.Stack[Delimiter]() - private val depthTracker = DepthTracker(0, 0, 0, false) + private val depthTracker = DepthTracker(0, 0, 0, 0) private val interpolationDepths = mutable.Stack[Int]() /** @@ -445,7 +445,7 @@ class Lexer(source: Source) extends Iterator[Token] { case ('<', '=') => advance2With(TokenKind.`<=`) case ('<', '>') => advance2With(TokenKind.`<>`) case ('<', '{') => - depthTracker.holes = true + depthTracker.holes += 1 advance2With(TokenKind.`<{`) case ('<', '~') => advance2With(TokenKind.`<~`) case ('<', _) => advanceWith(TokenKind.`<`) @@ -482,8 +482,8 @@ class Lexer(source: Source) extends Iterator[Token] { case ('$', _) => advanceWith(TokenKind.Error(LexerError.UnknownChar('$'))) - case ('}', '>') if depthTracker.holes => - depthTracker.holes = false + case ('}', '>') if depthTracker.holes > 0 => + depthTracker.holes -= 1 advance2With(TokenKind.`}>`) case ('}', _) if isAtInterpolationBoundary => interpolationDepths.pop() From ae6961293d050afadbdabf218fb02fc2d8cb3704 Mon Sep 17 00:00:00 2001 From: LTeuse Date: Fri, 10 Apr 2026 14:12:09 +0200 Subject: [PATCH 4/4] add check for issue #1301 --- examples/pos/issue1301.check | 2 ++ examples/pos/issue1301.effekt | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 examples/pos/issue1301.check create mode 100644 examples/pos/issue1301.effekt diff --git a/examples/pos/issue1301.check b/examples/pos/issue1301.check new file mode 100644 index 000000000..9709e4251 --- /dev/null +++ b/examples/pos/issue1301.check @@ -0,0 +1,2 @@ +<1> +test \ No newline at end of file diff --git a/examples/pos/issue1301.effekt b/examples/pos/issue1301.effekt new file mode 100644 index 000000000..0dfdcd2e2 --- /dev/null +++ b/examples/pos/issue1301.effekt @@ -0,0 +1,12 @@ +def main() = { + def f() = <{def g() = <{<"myFun">}>; 42}> + val x = println(s"<${1.show}>") + def g() = <{ + val x = 42 + <" here's a string "> + println(x) + <> + }> + val x = println(s"${"test"}") + def h() = println(s"<${"1" ++ <"string"> ++ <>}>") + () +} \ No newline at end of file