Repository navigation
v2 infer: a parameter containing a type variable inside a constructor is matched and judged, not admitted - #13187
Conversation
… matched and judged (WIP) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…wildcard) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ias controls Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ses unfolded before the exact judge; drop probe Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…90-generic-formal
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er (review 75350); refinement and record-wrapper controls Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Re review 75350: fixed the refinement-carrier case, but not by routing the mismatch to the judge, because that would reopen the hole this PR closes. Why not route to the judge. At a shape mismatch, What changed. On the cited case. Controls, claim_batch on head
|
…ucer for both routes; refinements never unfold; generic shortcut and alias arms deleted (review 75435) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… all alias-normalized at every infer obligation Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ike the formal Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Re review 75435: fixed by option 1, approved by calm-boar-904.
All 12 controls PASS on head. On main, every control exercising new behaviour FAILS (table in the PR body). The newly admitted sites from the floor census will be listed in the PR body once CI reports them. |
…90-generic-formal
Roster file keeps this change's side. Main added sixteen roster rows (#13187: gfi_*_reason in v2.test.claim.compiler.generic_formal_instantiation); their module is probed next. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, from probe 37238532455 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Why
In v2 infer, a call argument at a parameter that contains a type variable inside a constructor (
a: List<T>) was accepted whatever the argument's type:fn g<T>(a: List<T>, b: List<T>)called asg(1, 2)was accepted.g(xs_of_int, xs_of_pair)was accepted too.fn g(a: List<Int>, b: List<Int>)) refusesg(1, 2).DESIGN §4b puts "values inhabit declared types" on the compiler floor, so this was a below-floor wall that applied to every generic in v2.
Found while building XL-2 step 1's collection
concatroute (session/stern-swift-290). That route's refusal controls could not discriminate, because the roster row's signature is generic. calm-boar-904 ruled that this lands first, with concat stacked behind it.§6b: where it broke
v2.compiler.infer infer_judge_application_argument_instantiatingalready instantiates a parameter that is a type variable (x: T, theDeclaredTypeVariablearm).A parameter that only contains one took the
DeclaredTypearm and went to the declared-position judge as written.v2.std.inhabitance inhabitance_node_undecidable_reasonthen answeredUndecidableGenericFormal, which infer admits with a counted advisory.The argument's type was derived, so nothing about the call was undecidable. The undecidability came from never instantiating the parameter.
The repair: one matcher, the existing judge
infer_match_generic_formalmatches the parameter type first-order against the argument's derived type:Instantiationparameter needs anInstantiationargument with the same constructor and arity, and recurses on each pair of arguments;TypeVariableInstancelist.infer_type_instantiatedsubstitutes the bindings into the parameter type. The result goes to the sameinfer_judge_declared_positionevery parameter uses, so a variable bound twice to different instances refuses there. No second inhabitance relation is added.application_argument_does_not_inhabit, located at the application.infer_declared_type_obligationis the one producer of aDeclaredTypeObligationin infer. It presents the declared type, the produced type and the produced type's refinement carrier with every transparent alias unfolded (infer_type_alias_normalized). So the plain route and the generic route, and every declared position (argument, return, record field, let annotation, binder default, fold member and carrier), see one type per alias family.v2.std.inhabitanceholds no index, so the reading is supplied by the producer, exactly asproduced_declared_carrieralready was. The generic route's earlier shortcut and its match-time alias arms are deleted: it normalizes both sides once and ends at the same judge.fn p(a: FreeMonoid<Pair>)accepts aList<Pair>, which it refused before on spelling alone.where), and a nominal brand (spelledwhere brand(..),std.types WherePredicateMarker), never unfold (infer_alias_is_refined). So a raw carrier is never admitted where a refined or branded type is declared. Its carrier is reached only by the judge's one-step widening.refinement_declared_carrier, on a shape mismatch.TypeVariableInstanceexists in infer.infer_match_coproduct_of_typezips declared parameters against a scrutinee's arguments formatch, but doesn't match a pattern type against a produced one. This PR reuses its decomposer,infer_type_head_and_args.Controls (
v2.test.claim.compiler.generic_formal_instantiation)claim_batch ran on this head, then again with
src/v2/compiler/04_infer.dagchecked out from main, in one remote dispatch:Intat aList<T>parameter refusesTat two instances (List<Int>,List<Pair>) refusesList<List<T>>bindsT=Int, then aPairatT) refusesFreeMonoid<T>parameter,List<Pair>arguments, acceptedFreeMonoid<T>parameter,List<Pair>andList<Int>, refuseswhererefinement ofList<Int>atList<T>matches through its carrierList<T>and atList<Int>FreeMonoid<Pair>parameter takesList<Pair>and refusesList<Int>Posstill refuses a rawInt, plain and genericInt, admitsPos)claim_batch, remote. The "main" column is the same binary with
src/v2/compiler,src/v2/stdandsrc/v2/extdepschecked out from main.The refusal controls assert the reason
application_argument_does_not_inhabit, not merely "not accepted".Residue, stated
A type variable inside a shape this match doesn't open stays
UndecidableGenericFormal, as before:f: fn(T) -> T;T?;The next-rung trigger is in the new failure-mode row,
gunbc.recurring_failure_mode generic_formal_containing_a_type_variable_unjudged.Census of newly admitted sites (calm-boar-904's condition)
Instrument.
gunbc test //v2/test/parse/expression_bodied_fn_decl_parse:all(the N7 native route), run before and after:3f5d1154a2, after the v2: kinded type params; type applications checked in exact bijection; de-fork MachineWidth onto std.machine_constraints #13038 revert Revert #13038: main red on 62 lambda/fold claims #13293;eddcb4c5e7, which contains this PR.The
[native-verdict]chains were diffed link by link. Each occurrence was mapped to its file and an offset through[native-context-split]ranges, because occurrence numbers shift between builds.Result: no infer verdict on any chain flips from refused to accepted. The alias rule newly admits nothing in this population.
The only differences are resolve links:
concatrefusals inv2.std.nodeandv2.std.node_query(resolve_reason_unbound_symbol) are gone on the head. v2: bare concat over lists binds the std.algebra collection roster row and is typed by it (XL-2 step 1) #13207's roster route clears them, not this PR.countin both files (resolve_unbound_name_is_declared_in_several_modules).The required floor is also PASS on this head.
Limit, stated. This instrument reports only the chain each test stops on (neat-raven-383), not a verdict for every call site. A full per-site census needs v2 resolve/infer diagnostics over the whole closure, which no existing instrument prints. Within what is observable, the set of newly admitted sites is empty.
Freeze note (sharp-raven-357 amendment)
This PR adds
floor_cross_claim_pure_producers_warmrows (#13043 has not landed). Claim identities:v2.test.claim.compiler.generic_formal_instantiation.gfi_not_a_list_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_two_instances_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_nested_mismatch_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_one_instance_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_nested_agrees_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_alias_agrees_reasonv2.test.claim.compiler.generic_formal_instantiation.gfi_alias_disagrees_reasongfi_refined_carrier_reason,gfi_record_wrapper_generic_reason,gfi_record_wrapper_plain_reasongfi_plain_alias_agrees_reason,gfi_plain_alias_disagrees_reason,gfi_refined_refuses_raw_plain_reason,gfi_refined_refuses_raw_generic_reason,gfi_alias_of_refined_refuses_raw_reason,gfi_alias_of_refined_admits_refined_reason(same module)🤖 Generated with Claude Code