Skip to content

An explicitly-undef optional argument means absent, not bad - #125

Merged
revarbat merged 1 commit into
mainfrom
undef-means-absent
Aug 27, 2026
Merged

An explicitly-undef optional argument means absent, not bad#125
revarbat merged 1 commit into
mainfrom
undef-means-absent

Conversation

@revarbat

Copy link
Copy Markdown
Member

BOSL2 reaches a builtin whose name it also defines by wrapping it in builtins.scad — a file that use <>s nothing, so the names inside it bind to the builtins rather than to BOSL2's own definitions:

// builtins.scad -- includes nothing, only ever use<>d
function _linear_solve(A, b) = linear_solve(A, b);

That wrapper has a fixed signature, so a determinant()-style caller that passes no right-hand side still forwards b as undef. We treated that as a bad argument — warning on every call and returning undef, which made determinant() unusable through the wrapper.

before:  linear_solve(A)         -> det = 5
         linear_solve(A, undef)  -> WARNING, det = undef      <-- what the wrapper produces
after:   both give det = 5, silently

Passing undef is idiomatically the same as not passing in OpenSCAD — it's how BOSL2 threads optional arguments through wrappers everywhere (_cylinder(h,r1,r2,center,r,d,d1,d2) forwards all eight, most of them undef). So undef now counts as absent.

Same fix applied to levelset's isovalue, which had the identical problem waiting.

How it was found

By testing BOSL2's actual delegation plan rather than assuming it would work. First the mechanism, against OpenSCAD 2026.02.01:

function norm(v) = 999;   // in the caller
echo(direct  = norm([3,4]));    // 999 -- the caller's own wins
echo(wrapped = _norm([3,4]));   // 5   -- the builtin, via a use<>d wrapper

Identical on both our engines. Then the full linear_solve pattern end to end, which is where the undef problem surfaced. It now runs clean:

square    = [1, 3]
det       = 5
overdet   = [-1.4, 3.1]                        (least squares)
underdet  = [0.333333, 2.33333, 2.66667]       (minimum norm)
singular  = []                                 (BOSL2's convention, preserved by their wrapper)

No warnings, both engines.

Two regression tests added, one per builtin. 1064 tests pass under both engines.

Version 0.50.0 → 0.50.1.

🤖 Generated with Claude Code

BOSL2 reaches a builtin whose name it also defines by wrapping it in
builtins.scad, which use<>s nothing -- so the names in that file bind
to the builtins rather than to BOSL2's own:

    function _linear_solve(A, b) = linear_solve(A, b);

That wrapper has a fixed signature, so a determinant()-style caller
passing no right-hand side still forwards b as undef. We treated that
as a bad argument: warning on every call and returning undef, which
made determinant() unusable through the wrapper.

Passing undef is idiomatically the same as not passing in OpenSCAD --
it is how BOSL2 threads optional arguments through wrappers
everywhere -- so undef now counts as absent. Same fix for levelset's
isovalue, which had the identical problem waiting.

Found by testing BOSL2's actual delegation plan rather than assuming
it would work. The whole pattern now runs clean on both engines:
square [1,3], det 5, least-squares [-1.4,3.1], minimum-norm
[0.333,2.333,2.667], singular [], no warnings.

The wrapper mechanism itself was verified against OpenSCAD 2026.02.01
first: a function defined in a use<>d file that includes nothing sees
the builtin, while the caller's own definition still shadows it at the
call site. We match exactly.

1064 tests pass under both engines.
@revarbat
revarbat merged commit 1a1756a into main Aug 27, 2026
3 checks passed
@revarbat
revarbat deleted the undef-means-absent branch August 27, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant