Fix Edit Lambda for omitted optional arguments#174
Merged
Conversation
4a97d60 to
a8d8a6b
Compare
Edit Lambda expands a LAMBDA call into an equivalent LET. When the caller omitted optional arguments, the omitted parameters were never bound, yet the folded body still referenced them directly and via ISOMITTED(p) - which is meaningless outside a LAMBDA. This produced either a hard 0x800A03EC exception (generated lambdas, where the wrapper is a self-referential binding c, IF(ISOMITTED(c), 3, c)) or a #NAME? error (library lambdas like EXPLODE, where the wrapper binds a different name). Bind every parameter to a concrete value and strip all ISOMITTED logic so the result is the plain LET the author started with before LET to LAMBDA: - Supplied params -> the call-site argument. - Omitted params -> the default extracted from the canonical IF(ISOMITTED(p), default, p) wrapper, or NA() when none is found. - A folded wrapper whose binding name == param is merged in place to the resolved value (removes the self-reference that Excel rejects). - Every remaining ISOMITTED(x) is neutralised to FALSE, skipping string literals so help text is untouched. Optionality is not preserved in the LET (a LET has no notion of omitted args); the author re-applies it by re-running LET to LAMBDA, where the binding RHS becomes the default. This unblocks the LET to Lambda -> Edit Lambda -> LET to Lambda round-trip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a8d8a6b to
e291b9c
Compare
Collaborator
Author
|
Note: I force-pushed this branch. The previous commit (May 11) predated the net48 port and took a different approach — it stripped the IF wrapper to a bare |
Resolve analyzer/inspection warnings in the refactor code with no behaviour change: - Remove the dead `firstSpelling` dictionary in BuildDefaultPromotables (written but never read; `nameOrder` already tracks first-found spellings). - Switch explicit types to `var`, tidy `using` ordering, drop redundant named arguments, and normalise brace/formatting style. - Move the `PromotedInfo` and `LiteralToken` record declarations near their use sites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Closes #151.
Summary
When Edit Lambda expanded a call like
=EXPLODE(A1)where the LAMBDA declared optional parameters the caller omitted ([chunk_size],[horizontal]), the omitted parameters were never bound. The folded body still referenced them — directly and viaISOMITTED(p), which is meaningless outside a LAMBDA. This produced two failure modes with the same root cause:c, IF(ISOMITTED(c), 3, c). After folding that is a self-referential LET binding, which Excel rejects at formula-set time → hard0x800A03ECexception._size, IF(ISOMITTED(chunk_size), 1, chunk_size). No self-reference, so Excel accepts the string butchunk_sizeis undefined →#NAME?.Approach (agreed with Tim)
Reconstruct the plain LET the author started with before LET to Lambda — materialise every parameter to a concrete value and strip all
ISOMITTEDlogic. Optionality is not preserved in the LET (a LET has no notion of omitted args); the author re-applies it by re-running LET to Lambda, where each binding's RHS becomes the default. This unblocks theLET to Lambda → Edit Lambda → LET to Lambdaround-trip.Per parameter:
IF(ISOMITTED(p), default, p)wrapper found in any folded binding or the body;NA()when none is discoverable (valid LET, clear#N/A"supply this" marker).0x800A03ECand reproduces the original binding exactly (add(1,2)→c, 3;add(1,2,9)→c, 9).p, valuebinding is added before the folded bindings.ISOMITTED(x)is neutralised toFALSE(including standalone ones likeHelp?, ISOMITTED(text)), skipping string literals so help text is untouched.Examples
add==LAMBDA(a, b, [c], LET(c, IF(ISOMITTED(c), 3, c), a + b + c))=add(1, 2)→=LET(a, 1, b, 2, c, 3, a + b + c)(was: 0x800A03EC)=add(1, 2, 9)→=LET(a, 1, b, 2, c, 9, a + b + c)=EXPLODE(O13)→ valid LET bindingchunk_size, 1andhorizontal, FALSE, withHelp?, FALSEand the_size/_horizcalcs left as harmlessIF(FALSE, …). (was: #NAME?)Test plan
dotnet build addin/lambda-boss.slnx— 0 errorsdotnet test addin/lambda-boss.Tests/lambda-boss.Tests.csproj— 990/990 passingNA(), param-substring not matched, nested-IF default extraction, full-EXPLODE valid-LET.=LET(a,1,b,2,c,3,a+b+c)→ LET to Lambda (markcoptional, nameadd) →=add(1,2)→ Edit Lambda → expect=LET(a, 1, b, 2, c, 3, a + b + c), no exception, evaluates to6. And=EXPLODE(O13)→ Edit Lambda → valid LET (no#NAME?).🤖 Generated with Claude Code