Conversation
The Union branch of `apply_type_tfunc` did not handle `PartialTypeVar`
and widened to plain `Type`, so UnionAlls constructed in lowered code -
e.g. the keyword sorter's argtype check for
`f(v; y::Union{Nothing,<:Integer}=nothing)` - were rebuilt on every
call: a fresh TypeVar/Union/UnionAll plus a dynamic `isa`, costing 3
allocations and ~200ns per kwcall. Fold `PartialTypeVar` components
into `Type{Union{..., tv}}` (mirroring `_apply_type_tfunc`) so the
`isa` folds and the dead construction is eliminated.
Required adjacent fixes:
- the `Type` widening was unsound for possible TypeVar components:
`Core.apply_type(Union, Union{}, tv)` returns the bare TypeVar
object; widen to `Union{Type,TypeVar}` like the 2-argument case
already does. Symbolic TypeVars from `Type{B}` components still
yield `Type`, since they stand for a type equal to the TypeVar.
- `apply_type_nothrow` returned `true` for every Union application,
including throwing ones like `apply_type(Union, ::Any)`; validate
that each component is a `Type` or `TypeVar` instead.
Fixes JuliaLang#53917
This change was written with the assistance of generative AI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unlike `PartialTypeVar` components, whose runtime TypeVar object is created fresh on every execution, a `Const` TypeVar has known object identity, so `apply_type(Union, ...)` over it can be folded to a `Const` result: the runtime call computes the same function of egal inputs, and type egal compares Unions structurally with TypeVars by identity. This matches how `_apply_type_tfunc` already treats `Const` TypeVar parameters for non-`Union` heads. This change was written with the assistance of generative AI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `ConstsLattice` partial order only accepted `PartialTypeVar ⊑
TypeVar` exactly, returning `false` for any wider right-hand side such
as `Union{Type,TypeVar}` or `Any`. Since the instance of a
`PartialTypeVar` is a runtime TypeVar object, fall back to comparing
its widened type in the wider lattice instead. This makes the
`PartialTypeVar` special case in `apply_type_nothrow` unnecessary.
This change was written with the assistance of generative AI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # Compiler/src/tfuncs.jl # Compiler/test/inference.jl Besides the textual conflicts in apply_type_tfunc (kwalloc's PartialTypeVar/mayTypeVar handling vs. the TypeEq/TypeEgal split from JuliaLang#62001), this merge resolves a semantic conflict: kwalloc soundly widens `Union{...}` with a not-provably-`Type` argument to `Union{Type,TypeVar}`, which after JuliaLang#62001 surfaces in `promote_typejoin` (whose `typejoin` result infers `Any`) and degraded `eltype(::Tuple)` inference from `Type` to `Union{TypeVar,Type}`. Since `promote_typejoin` always returns a `Type` at runtime, assert that on its return value to restore the precision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `Union` head of `apply_type_tfunc` widens its result to `Union{Type,TypeVar}`
whenever a component may be a bare `TypeVar` at runtime, but the vararg
early-return still pinned a `Union` head with a trailing `Vararg` argtype to
`Type`. Since `Union{T}` is `T`, such a call can return a bare `TypeVar`, so the
inferred `Type` was unsound; widen it to `Union{Type,TypeVar}` to match the
non-vararg path.
Also simplify the folding loop: `hasvaluetv` is redundant with the already
computed `mayTypeVar` on every path that reaches the second loop, and the
`mayTypeVar` update recomputed `hasintersect(ai, TypeVar)`. In the
`⊑(::ConstsLattice)` method for `PartialTypeVar`, reuse `widenconst` and keep the
`b === TypeVar` fast path.
Co-Authored-By: Claude Fable 5 <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.
Keyword arguments declared with sugared unions like
Union{Nothing,<:Integer}cost several allocations (~200ns) per call, because lowering splices the declared-type expression into the keyword sorter for theisacheck, and theUnionhead ofapply_type_tfunchad noPartialTypeVarcase — so the runtime_typevar/apply_type/UnionAllconstruction never folded.This teaches the
Unionbranch ofapply_type_tfuncto:PartialTypeVarcomponents intoType{Union{..., tv}}, preserving the TypeVar identity needed for the laterabstract_call_unionallrewrap, after which theisaguard folds and the construction is DCE'd;ConstTypeVar components toConst(their object identity is known);Union{Union{}, tv}is the bareTypeVarobject at runtime, not aType, so results distinguish value TypeVars (TypeVar), symbolic TypeVars fromType{B}components (Type{ty}), and unknown components (Union{Type,TypeVar}). The pre-existinghasnonType → Typeshortcut was unsound for exactly this reason.apply_type_nothrownow validatesUnioncomponents againstUnion{Type,TypeVar}instead of blindly returningtrue, and⊑(::ConstsLattice)learns to comparePartialTypeVaragainst types wider than plainTypeVar.The merge with master (past JuliaLang#62001's
TypeEgalkind) surfaced one semantic interaction: the soundUnion{Type,TypeVar}widening reachespromote_typejoin(whosetypejoincall infersAny), degradingeltype(::Tuple)inference now that_compute_eltypeis reachable in the equality-keyed world. Sincepromote_typejoinalways returns aTypeat runtime, a::Typeassertion on its return value restores the precision.With this, the keyword sorter for a
Union{Nothing,<:Integer}kwarg contains no_typevarcalls or foreigncalls, and both repros from the issue run allocation-free.Fixes JuliaLang#53917
This pull request was written with the assistance of generative AI (Claude).
🤖 Generated with Claude Code