Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d3427d9
feat: add user-defined action overloading with arity- and type-based …
claude Jul 20, 2026
c0b6453
docs: validate overloading examples against the release binary
claude Jul 20, 2026
090ad3e
chore: track container-method overloading deferral in issue #638
claude Jul 20, 2026
be2a1c9
fix: address PR #639 review findings
claude Jul 20, 2026
103912c
fix: accept 'as any' in type position and treat Any as non-concrete f…
claude Jul 20, 2026
1c7d6ca
test: consolidate overload test files into one crate to unblock CI li…
claude Jul 20, 2026
769e898
fix: match primitive type names case-insensitively in action annotations
claude Jul 20, 2026
f48cb41
fix: address maintainer deep-review findings on overloading
claude Jul 20, 2026
5f8bede
fix: guard action-alias updates and clarify compat tuple order
claude Jul 20, 2026
2dbb011
fix: snapshot-aware action aliases, flow-safe alias state, scoped run…
claude Jul 20, 2026
dc72754
fix: post-review polish — branch-helper doc, runtime duplicate messag…
claude Jul 20, 2026
7ebc8d0
fix: path-sensitive overload counters, per-block runtime enforcement
claude Jul 20, 2026
698e122
fix: restore reset-first ordering in interpret_inner
claude Jul 20, 2026
4a2e7b3
docs: reconcile diary test counts across review rounds
claude Jul 20, 2026
6a2ab25
fix: nothing arguments no longer inflate overload specificity
claude Jul 20, 2026
82444a3
fix: block-entry enforcement arming, abrupt-exit-aware alias joins
claude Jul 20, 2026
37e7e14
docs: comment accuracy — multi-candidate survival isn't only Unknown …
claude Jul 20, 2026
3ad243e
fix: alias flow for all loop variants, revertible enforcement arming
claude Jul 20, 2026
5937006
fix: budget breaches survive the loop-body diagnostic demotion
claude Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 360 additions & 0 deletions Dev diary/2026-07-20-action-overloading.md

Large diffs are not rendered by default.

162 changes: 162 additions & 0 deletions Docs/03-language-basics/actions-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,168 @@ end check
store double sum as (sum of 5 and 3) times 2
```

## Defining Multiple Versions of an Action (Overloading)

An action name can have several definitions in the same scope, as long as WFL
can always tell them apart at a call site. Each version is called an
*overload*, and calls pick the right one automatically.

### Overloading by parameter count

The simplest overloads differ in how many parameters they take:

```wfl
define action called greet with parameters name:
return "Hello, " with name with "!"
end action

define action called greet with parameters first and last:
return "Hello, " with first with " " with last with "!"
end action

display greet of "Alice" // Hello, Alice!
display greet of "Alice" and "Smith" // Hello, Alice Smith!
```

### Overloading by parameter type

Same-count overloads are allowed when their parameters declare different
types with `as`:

```wfl
define action called depict with parameters value as number:
return "a number: " with value
end action

define action called depict with parameters value as text:
return "some text: " with value
end action

display depict of 42 // a number: 42
display depict of "wfl" // some text: wfl
```

Dispatch happens on the actual value, so calling through a variable works the
same way: `depict of some_variable` runs the number version when the variable
holds a number.

### The two rules

Two same-name definitions are rejected when a call could never be routed
deterministically between them:

1. **Exact duplicates** — same parameter count and same declared types
(untyped parameters count as "accepts anything"):

```wfl
define action called f with parameters x:
return 1
end action

define action called f with parameters y: // error: same parameters
return 2
end action
```

2. **Indistinguishable pairs** — same parameter count with no position where
*both* versions declare concrete, different types. An untyped parameter
accepts numbers too, so `f with x` and `f with x as number` would both
match `f of 5`:

```wfl
define action called f with parameters x:
return 1
end action

define action called f with parameters x as number: // error: ambiguous
return 2
end action
```

Give every same-count overload distinct parameter types (`as number`,
`as text`, ...), or use different parameter counts.

### How calls choose an overload

1. Versions with the wrong parameter count are dropped. If none remain, the
call is an error listing the counts the action accepts.
2. Among same-count versions, each typed parameter is checked against the
argument. Versions whose declared types reject an argument are dropped.
3. If several versions still match (possible with container types and
inheritance), the one with the most concretely-matched parameters wins;
remaining ties go to the version defined first.
4. If no version matches, the error lists what you provided and every
version's signature.

When argument types cannot be known until the program runs, static analysis
defers the choice to the runtime silently — you never have to annotate a call.

A few matching rules worth knowing:

- **`nothing` matches every typed parameter.** Passing `nothing` never
disqualifies a version, and it adds no specificity to typed parameters —
when several versions accept it, the one defined first wins. The one
exception is a parameter declared `as nothing`: that is an exact match for
a `nothing` argument, so such a version wins over the others.
- **Container types include descendants.** A parameter typed `as Dog` accepts
a `Dog` instance and any container that `extends Dog` (directly or through
a chain).
- **Overloaded actions enforce their declared types when they run.** Calling
a version of an overloaded action with a value its parameter type rejects
is a runtime error naming the parameter and both types — an overload never
silently runs with an argument its signature rules out. An action defined
only once keeps its historical behavior: its annotations guide static
analysis and do not reject values at runtime. This enforcement is scoped
to the block that defines the versions: a different block's lone action of
the same name is a separate, single action and keeps the historical
behavior. When a block adds a version to an action that already exists in
the same scope, the existing version starts enforcing the moment that
block begins executing — a call between the block's start and the new
definition already dispatches strictly. If the block exits before the new
definition ever executes (an error, an early return), the arming is
undone: a never-merged action returns to its single-action behavior.
- **Stored actions dispatch the same way — over the versions that existed
when stored.** After `store helper as depict`, calling `helper of 42`
resolves among `depict`'s versions exactly as a direct call would. The
stored reference is a snapshot: versions of `depict` defined *after* the
`store` belong to `depict` but not to `helper`, both when the program runs
and in static analysis.
- **Versions defined inside a branch or loop dispatch at runtime.** When a
version is defined inside a `check if` branch or a loop body, whether it
exists depends on what actually executed — so static analysis stops
guessing for references stored after that point and defers their calls to
runtime dispatch, which always judges against the versions that really
exist. The same applies to a stored reference reassigned anywhere inside
a loop or `try` body: a `break` or an error can leave the loop (or reach
the error handler) while the reference holds an intermediate binding, so
its calls defer to runtime dispatch too.

### Overloads behave like one action

An overloaded name is used exactly like a single action — the same call
syntax, the same rules everywhere the name appears:

```wfl
define action called shout with parameters message as text:
return touppercase of message
end action

define action called shout with parameters message as number:
return "LOUD NUMBER " with message
end action

display shout of "quiet" // QUIET
display shout of 3 // LOUD NUMBER 3
```

If one overload takes no parameters, a bare reference to the name auto-calls
it — the same behavior as a single zero-parameter action.

> **Not yet overloadable:** container methods (actions defined inside
> `create container`) do not support overloading yet; a repeated method name
> keeps the last definition (tracked in
> [#638](https://github.com/WebFirstLanguage/wfl/issues/638)).

## Variable Scope

### Local Variables
Expand Down
33 changes: 33 additions & 0 deletions Docs/reference/language-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,39 @@ define action called <identifier> [with parameters <param-list>]:
end action
```

Parameters may declare a type with `as` (e.g. `value as number`). An action
name may be defined more than once in the same scope (overloading) when every
pair of same-name definitions differs in parameter count or has at least one
position where both declare concrete, different parameter types. Exact
duplicates, and same-count pairs with no such distinguishing position, are
definition-time errors. Calls resolve by filtering candidates on argument
count, then on argument types (statically when known, otherwise on the runtime
values); among several runtime matches the version with the most
concretely-matched parameters wins, with remaining ties resolved in definition
order. A `nothing` argument is compatible with every parameter type and
contributes no dispatch specificity except toward a parameter declared
`as nothing`, which it matches exactly; a parameter annotated with a
container name accepts instances of that container or any descendant via
`extends`. For an action participating in overloading,
declared parameter types are enforced when the action executes — a call whose
argument a declared type rejects is a runtime error, so a call between two
definitions dispatches over the overloads defined so far. This enforcement is
scoped to the statement block whose definitions form the overload set; for
definitions in different blocks that merge into one scope, enforcement of
the existing members begins when the block containing the merging
definition starts executing, and is reverted when that block exits without
the merge having executed. An action defined exactly once in its block
(and never merged) is not runtime-checked; its annotations inform static
analysis only. A variable bound to an action by a
bare reference (`store h as f`) is callable and dispatches with the
signatures the action had at the point of the binding (snapshot semantics),
statically and at runtime; a binding whose state cannot be determined
statically — reassigned in one branch of a conditional, reassigned anywhere
inside a loop or `try` body (an abrupt exit can expose the intermediate
binding), or bound after a definition that sits inside a branch or loop —
defers wholly to runtime dispatch. Container methods do not support
overloading.

**Action Call:**
```
call <identifier> [with <argument-list>]
Expand Down
18 changes: 16 additions & 2 deletions Docs/reference/syntax-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,24 @@ define action called add_numbers with parameters x and y:
return x plus y
end action

// Call
// Call as a statement
call add_numbers with 5 and 3
store result as add_numbers with 5 and 3

// Call as an expression
store result as add_numbers of 5 and 3
display result

// Typed parameters and overloading: same name, distinguishable signatures
define action called depict with parameters value as number:
return "a number: " with value
end action

define action called depict with parameters value as text:
return "some text: " with value
end action

display depict of 42 // dispatches to the number version
display depict of "wfl" // dispatches to the text version
```

## Lists
Expand Down
Loading
Loading