When merging URIs where the base has `path: ""`, the internal `:+` marker
used to track the join point between base and relative paths was being
converted to a literal "+" in the output.
* Implement gradual function domains and tighten function application
Add fun_domain/1 to compute function domains for static, dynamic, and mixed function types, including explicit :badfun and {:badarity, ...} outcomes when domain extraction is not well-defined.
Refine gradual function application internals by separating static and dynamic normalization paths, documenting behavior for purely dynamic and mixed cases, and clarifying domain/compatibility handling.
Expand descr tests with gradual-application edge cases and a dedicated fun_domain test suite covering static, dynamic, mixed, and arity-error scenarios.
* Refactor function application and domain handling
* Remove fun_domain, recover coverage via fun_apply tests
fun_domain was only used in tests; its behavior is fully observable
through fun_apply (wrong-domain arg → :badarg, right-domain → :ok).
Removed it and converted the domain tests to fun_apply assertions,
adding a few previously uncovered cases along the way: pure non-fun
badfun, union-arg-type enforcement, 2-arity, mixed-arity badarity
for both static and dynamic unions, and the mixed static+dynamic
gradual domain case.
* Remove dead helpers introduced alongside fun_domain
fun_single_arity, fun_single_arity_pair, fun_non_empty_arities_of,
and fun_non_empty_arities are now unreachable. Also revert
dynamic_fun_top? to the simpler map == %{} check — the semantic
emptiness scan added in the refactor was only needed to handle
the same edge cases fun_domain was handling.
* Move mixed-arity badarity check to static side of fun_normalize_both
fun_normalize itself stays simple (no arity cross-check). The check
for other non-empty arities now lives in fun_normalize_both via
fun_other_non_empty_arities/2, and only fires for the static
component. This means:
- union(fun/1, fun/2) applied with 1 arg → {:badarity, [1, 2]}
(static: we know one branch will always fail at runtime)
- union(dynamic_fun/1, dynamic_fun/2) applied with 1 arg →
{:ok, dynamic(integer())} (dynamic: picks matching-arity arrows,
wraps in dynamic() to reflect that the other branch may fail)
* Add tests for mixed-arity union behavior
Static union: badarity regardless of which arity is called with,
with the called arity listed first followed by the others.
Dynamic union: picks the matching-arity arrows and wraps in dynamic(),
gives badarity only when no arity matches at all, and falls back to
dynamic() when the arg is outside the domain but dynamically compatible.
* Remove compatibility code for OTP26-
* Remove support for application/erlang+html docs v1
* Remove workaround for bug in older OTP versions
* Update OTP version checks
* Remove OTP25- skipping in tests
* More test cleanup
The `name()` type for PartitionSupervisor includes `{:via, module(), term()}`
tuples, but `count_children/1` and `stop/3` only accepted atoms, raising
`FunctionClauseError` for via tuples.
When one argument is 0 and the other is negative, `extended_gcd/2`
was returning a negative GCD, violating both the @spec (which
declares `non_neg_integer`) and consistency with `gcd/2`.
The validation was asymmetric - it rejected positive digits >= base
but silently accepted negative digits <= -base. This fix adds the
missing check for negative out-of-range digits.
`Float.parse/1` was returning `:error` for overflow in scientific notation (e.g.,
"1.0e400") but raising `ArgumentError` for equivalent overflow in non-scientific
notation (e.g., a 310-digit number). This violated the documented behavior that
states `Float.parse/1` should return `:error` when the float exceeds the maximum
size.
When `dereference_symlinks: true` was set and there was a symlink cycle
(e.g., a -> b -> a), the function would infinitely recurse. This fix
tracks visited paths using a `MapSet` and returns `{:error, :eloop, path}`
when a cycle is detected.
The previous implementation was incorrectly nesting differences.
So if we had four clauses, the four clause was computing the type as:
clause4 - (clause3 - clause2 - clause1) - (clause2 - clause1) - clause1
While it should have been:
clause4 - clause3 - clause2 - clause1
This means that instead of `n` differences, we had an arithmetic progression
of `n * (n + 1) / 2` differences.
When destination is a subdirectory of source, `cp_r` would enter an
infinite loop because the newly created destination directory would
be included in the file listing during recursion. This can **cause disk
exhaustion** if large files are present under source.
This adds a validation check that returns `{:error, :einval, destination}`
when copying on a subdir of the source path.
Also document typical error reasons including the new `:einval` case.
The optimized count implementation was counting newline characters
instead of actual lines, returning incorrect results for files that
don't end with a newline (which is common for source code files).
For example, a file with content "line1\nline2\nline3" (3 lines)
was returning 2 instead of 3.
The fix tracks the last byte read and adds 1 at EOF if the file
has content but doesn't end with a newline.
Previously, passing a non-list binding to `Code.eval_string/3` would crash
deep in Erlang code with a confusing `FunctionClauseError` in
`:elixir_erl_var.load_binding/6`.
When slicing a Range with step > 1 using a slice range that also has
step > 1, the formula for calculating the step in the slicer function
was incorrect.