Status
Accepted
Context
Slug supports function overloading through FunctionGroup dispatch by signature.
In practice, a module can define a callable name from two sources:
- A foreign declaration, e.g.
foreign trim = fn(@str str) - A local function value, e.g.
val trim = fn(@str s, @str prefix) { ... }
Before this decision, the second declaration could fail with:
val 'trim' is already defined as a 'val' and cannot be reassigned
The binding immutability check ran before callable-merge behaviour, so valid overload declarations were treated as illegal reassignment. At the same time, duplicate signatures were silently overwritten in some merge paths, which made overload conflicts implicit.
Decision
Slug allows mixed callable declarations under one symbol when they contribute distinct signatures, while preserving immutable val behaviour for non-callables.
Callable Binding Rule
When a name already exists as an immutable binding:
- If the incoming value is callable (
Function,Foreign, orFunctionGroup) and the existing value is callable, merge it into the sameFunctionGroup. - If the existing value is non-callable, keep the previous immutability error behaviour.
Duplicate Signature Rule
When merging callable declarations for the same name:
- Reject duplicate signatures with an explicit error.
- Do not overwrite an existing implementation for the same signature.
Non-callable Immutability Rule
No behaviour change for non-callables:
- Reassigning or redefining an immutable
valthat is not part of callable overloading remains an error.
Consequences
Positive
- Enables expected overloading across
foreignand localval fndeclarations. - Makes overload conflicts explicit by rejecting identical signatures.
- Preserves existing immutability guarantees for non-callable values.
- Reduces import-time failures for modules that combine foreign and local callable variants.
Negative
- Adds additional branching and validation in environment binding logic.
- Introduces a new explicit error path for duplicate signatures that callers may need to handle.
Neutral
- Overload dispatch semantics remain unchanged; this decision affects binding-time behaviour only.
- Existing code that does not mix callable declarations under one name is unaffected.