Repository navigation
interp: refuse a return the macro cannot place, at the return - #615
Open
notactuallytreyanastasio wants to merge 1 commit into
Open
notactuallytreyanastasio wants to merge 1 commit into
notactuallytreyanastasio wants to merge 1 commit into
Conversation
A `return` inside a lambda with a declared result type, `(): T => ...`,
never desugars, and no error says so. That includes every generator
and async block, which are typed lambdas. The return macro looks only
for a function declared with `fn` or `let`, so that a return lands
somewhere predictable rather than at the end of a lambda that some
control flow desugared into. A grammar lambda is not one, so the macro
found nothing and returned a bare Fail that logged nothing. The
`return` call survived the syntax macro stage,
promoteOutTypesToReturnDecls declared the lambda's result variable
under the parsed name `return`, and name resolution bound the leftover
call to it. `return 1;` became a call of the lambda's own result
variable, and the error showed up somewhere else.
At origin/main, with `let call(g: fn (): Int): Int { g() }`:
let r = call { (): Int =>
if (true) { return 1; }
2
};
@t: return__6 is not initialized along branches at [:3+14]
py: TypeError: 'NotImplementedType' object is not callable
rust: error[E0618]: expected function, found `!`
The same lambda inside `let wrap(): Int { call { ... } }`:
@t: break/continue not within a matching block
js: SyntaxError: Undefined label 'fn_5'
py: NameError: name '_fn' is not defined
rust: error[E0426]: use of undeclared label `'fn__6`
`if (i == 1) { return; }` inside a
`{ (): GeneratorResult<Empty> extends GeneratorFn => ... }` block gives
the same "return__6 is not initialized" and the same runtime failures.
A top-level `return;` gives "@g: Expected function type, but got
Function" and "Cannot translate Invalid", and js prints "before" and
then throws.
After, each is one error at the `return`, in the syntax macro stage:
[-work/t.temper:3+14-22]@s: `return` inside a lambda with a declared
result type would leave the enclosing function, not the lambda. Make
it a function with `fn` or `let`, or end a generator with `break` or
by reaching its end
[-work/t.temper:2+0-6]@s: Return outside function body
The second is ReturnOutsideFn, which the macro already built and then
dropped unless the stage was past SyntaxMacro. The macro runs only in
that stage, so it never logged.
Returning from a function through a body that control flow desugared
into a lambda is unchanged. These print the same at origin/main and
with this change on js, py, rust and java: a typed `fn` lambda
returning, a `let` function nested in another, a typed lambda holding
a `fn` that returns, a getter, a static method, a return in an
`orelse do { ... }`, in `when` arms, in a `do` block and in a `while`
loop.
Giving the lambda's result variable a fresh name instead breaks every
typed lambda, because later passes recognize that declaration by its
parsed name, so the fix is in the macro.
New stage test syntax-macro/return-from-lambda: three ReturnFromLambda,
one ReturnOutsideFn, and nothing for a for-of return. It fails with the
ReturnDesugarMacro change reverted. :log:jvmTest 33, :interp:jvmTest
76, :frontend:jvmTest 704 (InterpreterFunctionalTests included),
:be-js:jvmTest 197, the py, rust and java 17 functional suites at 64
each, :build-user-docs:test 76, 0 failures; ktlintCheck and detekt
pass.
What does not work: whether `return` in a generator or async block
should end that block is a language question this does not answer; it
only says why it is not allowed. A program with these errors still
emits code, as any program with errors does, and that code panics when
it reaches the error.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Bobby Grayson <bobbbygrayson+github@gmail.com>
Contributor
|
Can we have a test that you can still return from a lambda that is inlined, i.e. that this check does not happen too early. These two are equivalent: export let f(xs: List<Int32>): Int32 {
for (let x of [1, 2, 3]) {
if (x == 2) { return x }
}
-1
}
// ----
export let g(xs: List<Int32>): Int32 {
xs.forEach { x =>
if (x == 2) { return x }
}
-1
}Some of the iterator style methods are marked with a decoration ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
returninside a lambda with a declared result type,(): T => ...,never desugars, and no error says so. That includes every generator
and async block, which are typed lambdas. The return macro looks only
for a function declared with
fnorlet, so that a return landssomewhere predictable rather than at the end of a lambda that some
control flow desugared into. A grammar lambda is not one, so the macro
found nothing and returned a bare Fail that logged nothing. The
returncall survived the syntax macro stage,promoteOutTypesToReturnDecls declared the lambda's result variable
under the parsed name
return, and name resolution bound the leftovercall to it.
return 1;became a call of the lambda's own resultvariable, and the error showed up somewhere else.
At origin/main, with
let call(g: fn (): Int): Int { g() }:The same lambda inside
let wrap(): Int { call { ... } }:if (i == 1) { return; }inside a{ (): GeneratorResult<Empty> extends GeneratorFn => ... }block givesthe same "return__6 is not initialized" and the same runtime failures.
A top-level
return;gives "@g: Expected function type, but gotFunction" and "Cannot translate Invalid", and js prints "before" and
then throws.
After, each is one error at the
return, in the syntax macro stage:The second is ReturnOutsideFn, which the macro already built and then
dropped unless the stage was past SyntaxMacro. The macro runs only in
that stage, so it never logged.
Returning from a function through a body that control flow desugared
into a lambda is unchanged. These print the same at origin/main and
with this change on js, py, rust and java: a typed
fnlambdareturning, a
letfunction nested in another, a typed lambda holdinga
fnthat returns, a getter, a static method, a return in anorelse do { ... }, inwhenarms, in adoblock and in awhileloop.
Giving the lambda's result variable a fresh name instead breaks every
typed lambda, because later passes recognize that declaration by its
parsed name, so the fix is in the macro.
New stage test syntax-macro/return-from-lambda: three ReturnFromLambda,
one ReturnOutsideFn, and nothing for a for-of return. It fails with the
ReturnDesugarMacro change reverted. :log:jvmTest 33, :interp:jvmTest
76, :frontend:jvmTest 704 (InterpreterFunctionalTests included),
:be-js:jvmTest 197, the py, rust and java 17 functional suites at 64
each, :build-user-docs:test 76, 0 failures; ktlintCheck and detekt
pass.
What does not work: whether
returnin a generator or async blockshould end that block is a language question this does not answer; it
only says why it is not allowed. A program with these errors still
emits code, as any program with errors does, and that code panics when
it reaches the error.
🤖 Generated with Claude Code