checker: From<Scalar> on a generic type is refused as overlapping the blanket From<Never> #455
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Found while implementing #83 (
std/gradient, PR #454).public type Dual<Scalar: Real = Float> with ..., From<Scalar>(orextend<Scalar: Real> Dual<Scalar> with From<Scalar>) is rejected:The other implementation is
extend<Target> Target with From<Never>instd/core/src/convert.trb. Same family as #441 (overlap check of impls with a generic parameter), but the cause is a different one (see Cause).Cause (refine, against
mainafter #441 / PR #448)reportOverlapincompiler/src/semantics/checker/implementation.trbunifies the two targets and the two trait applications over one shared substitution (unifyTerms, with occurs check, from #441). For this pair that givesTarget = Dual<Scalar>andScalar = Neverand no conflict, so it reports the overlap. The overlap is real in the checker's terms:Dual<Never>has twoFrom<Never>.The first version of this issue assumed the bound
Scalar: RealrulesNeverout. It does not:resolveUncacheddeliberately answers every bound onNeveras holding (a bound onNeverholds whatever the trait is, needed forSource<Item, Never>). A fix that consults bounds in the overlap check would therefore not work.What makes the overlap harmless is the other side:
From<Never>::from(value: Never)can never be called, because no value ofNeverexists, so two implementations that meet only at aNeversource cannot be told apart by any program. Everything about the blanket is already special-cased throughchecker.wellKnown.fromandchecker.neverType(fitsTyperefuses it for any argument but aNever).Fix location
reportOverlap(compiler/src/semantics/checker/implementation.trb), after the twounifyTermscalls and beforereportBlanketOverlap: skip the report when one implementation is theFrom<Never>blanket and the other implementation's singleFromargument is a bare generic parameter. The solutions of that pair are exactlyparameter = Never.isNeverSourceBlanket):targetShapeOf(target)is.Blanket,capabilitySymbolOf(trait) == checker.wellKnown.from, and the one trait argument ischecker.neverType. No new name looked up by string.Parameter(walk it withwalkTerm).extend Foo with From<Never>written by hand stays a real overlap with the blanket;From<Int>besideFrom<Scalar>on one generic type stays one too (the solutionScalar = Intis notNever).Fromis exempt. Other traits keep the plain rule: their members can return or use the argument type, so aNeverinstance is not provably unobservable.Acceptance criteria
extend<Scalar> Wrapper<Scalar> with From<Scalar>(a generic type that takes its own parameter asFromsource, bounded or unbounded) is accepted bytorb checkbeside the std blanketFrom<Never>;type Wrapper<Scalar: Real> with From<Scalar>too (thewithlist form).compiler/tests/implementations.test.trbpins, next to the #441 tests: (a) the two forms above are accepted (problems(source).isEmpty(); the test workspace containsstd/*, so the blanket is present); (b)extend Wrapper<Int> with From<Never>beside it is still refused with the unchanged message; (c)From<Scalar>plusFrom<Int>on one generic type is still refused with the unchanged message; (d) the existing overlap and blanket-message tests stay green unmodified.tests/language/(with.expected) declares such a type, callsWrapper.from(x)/.into()with aScalarvalue and gets the right implementation undertorb runandtorb run --native. If call resolution reports ambiguity with the blanket for a concreteScalar, fixing that is part of this issue;fitsTypealready refusesNeverfor any other argument.Dualinstd/gradient(std/gradient/src/dual.trb) getsFrom<Scalar>(a constant, rate zero, same asDual.constant) with a test instd/gradient/tests;docs/design/COMPUTE.mdsection 4 drops the pitfall bullet ("Dual<Scalar>has noFrom<Scalar>...") and names the conversion;skills/regenerated (docs skill).sh tools/gates.sh agreen in forge CI (not run locally).Commit plan (one PR)
fix(checker): let a generic type take its own parameter as the source of From beside the Never blanket- the exemption, the compiler tests, thetests/language/program.feat(std/gradient): add From<Scalar> to Dual-dual.trb, its test, theCOMPUTE.mdbullet, regeneratedskills/.Seed
Does not touch the seed: checker change only (no syntax, no new native, no driver flag, no name looked up by string). Commit 2 changes
std/gradient, which the seed does not use. Dependencies: #441 / PR #448 (merged) and #83 / PR #454 (merged); none open.Dualnumbers (forward mode) #83[loop:torbscript] claim refine by session:loop-torbscript-20261024 until 2026-10-24T04:24:35Z
[loop:torbscript] refine: ready, claim released, next: implement
Definition of ready
docs/design/COMPUTE.mdsection 4 (theDualpitfall bullet) andCONCEPT.md"Coherence". The rule "two implementations of one trait may not overlap" stays; one provably unobservable overlap is exempted.std/gradientaddition; no syntax, native, driver flag or string-looked-up name).Finding that changes the premise: the issue assumed
Scalar: RealexcludesScalar = Never. It does not, because a bound onNeverholds whatever the trait is (resolveUncached, needed forSource<Item, Never>). So bound-aware disjointness is the wrong fix.decided by the loop:
From<Never>blanket when the otherFromtakes a bare generic parameter, inreportOverlapafter the shared-substitution unification. Why: the only common instance isparameter = Never, andfrom(value: Never)can never be called, so no program can tell the two apart. The checker already special-cases this blanket throughwellKnown.fromandneverType.From, not every trait. Why: other traits can return or use the argument type, so an instance atNeveris not provably unobservable; keeping the rule narrow loses nothing the issue needs.Neveralways hold, and removing the blanket breaks?onResult<_, Never>(a published std behaviour). Technical checker design, no owner question.No owner question: none of the contract's categories applies.
[loop:torbscript] claim implement by session:loop-torbscript-20261024 until 2026-10-24T04:32:11Z
[loop:torbscript] implement: checkpoint (context limit reached, not finished), next: continue implement
Done (uncommitted in worktree
code/wt/torbscript-language-455-from-never-overlap, branchloop/455-from-never-overlap, nothing pushed yet):compiler/src/semantics/checker/implementation.trb:reportOverlapskips the report via newmeetsOnlyAtNeverSource/isNeverSourceBlanket(blanket =.Blankettarget, traitFrombywellKnown.from, single argumentchecker.neverType; other side = singleFromargument that is a bare.Parameter). Not yet compiled or format-checked (no localtorbbinary found; use CI or the repo build).Left:
fix(checker): ...: tests incompiler/tests/implementations.test.trbnext to the #441 tests (accepted:extend<Scalar> Wrapper<Scalar> with From<Scalar>bounded and unbounded, and thetype Wrapper<Scalar: Real> with From<Scalar>list form; refused unchanged:extend Wrapper<Int> with From<Never>,From<Scalar>plusFrom<Int>), plus atests/language/program with.expected.feat(std/gradient):From<Scalar>onDual, test, drop the COMPUTE.md pitfall bullet,docs skillregenerate, pdisc stamp+check.torb format --checkon touched files.[loop:torbscript] implement: PR #461 open (2 commits, rebased on main, commit-message gate fixed: subject was 102 chars). Both commits written; code never compiled locally, waiting for CI. Next: fix from CI output, final
implementcomment, remove worktree.[loop:torbscript] release implement: the continue worker timed out at 02:33Z; its work landed as PR #461 (see the 03:11Z comment). next: waiting (forge CI of PR #461)
[loop:torbscript] claim fix by session:loop-torbscript-20261024 until 2026-10-24T07:26:45Z
[loop:torbscript] fix: bootstrap (linux-x64) of run 1569 failed in step 1 with the seed:
Cannot findholderhere(compiler/tests/implementations.test.trb:274).takesScalarcalled the sibling group helperholder, and a fn declared in agroupbody does not see its siblings; foldedholderintotakesScalar(commit6ececbd3, test-only). No local compiler on this box, so the checker code and the other new tests are untested beyond a read-through (names and forms checked against existing call sites). next: waiting (forge CI of6ececbd3)