Skip to content

Commit c47657a

Browse files
committed
TC: post-inline subsumption filter for op disambiguation
[gen_select_op]'s [drop_subsumed_tc] classifies candidates by the [op_kind] of their declared path — but after typing, abbrev candidates are inlined and their bodies' heads are what actually appear in the elaborated term. So an abbrev whose body is itself a TC-op invocation escapes the filter even when, post-inline, it's a TC-op-headed term that reduces to the same head as another candidate. Concrete trigger: writing [p * q] with [p, q : int poly] yielded [MultipleOpMatch] because three [*] candidates survived: - [Top.TcPoly.*] (abbrev → body head [polyM], non-TC) - [Top.TcMonoid.*] (abbrev → body head [(+)], TC op of monoid) - [Top.TcRing.*] (TC class op → reduces to [polyM]) [drop_subsumed_tc] correctly drops [Top.TcRing.*] (TC op subsumed by [polyM] in concrete_paths), but it leaves [Top.TcMonoid.*] alive because its declared kind is [OB_nott] (abbrev), not [OB_oper(OP_TC)]. The body's head [(+)] is a TC op, but the filter never inspects it. Compare to [+] at the same carrier: only two candidates, the second being the [Top.TcMonoid.+] class op directly. [drop_subsumed_tc] sees its declared kind as [OB_oper(OP_TC)], runs [tc_reduce], drops it. The asymmetry is just that TcMonoid ships an [abbrev (*) ['a <: mulmonoid] = (+)<:'a>] but no analogous abbrev for [+]. Fix: add [drop_subsumed_by_post_inline_head], a sibling of [drop_subsumed_tc] that operates on the post-inline body head rather than the declared op_kind. For each candidate with a body, force the [sbody] lazy and extract the body's head op. Collect the non-TC heads as [concrete_heads]. Then drop any candidate whose post-inline head is a TC op that [tc_reduce]s to a head already in [concrete_heads]. Run after [drop_tc_bounded_notation] so the existing pre-inline filters dedupe what they can first. After the fix, [p * q] at carrier [int poly]: - [Top.TcPoly.*] body head [polyM] — kept - [Top.TcMonoid.*] body head [(+)] (TC) — tc_reduce → [polyM] in concrete_heads → dropped - [Top.TcRing.*] already dropped by [drop_subsumed_tc] => single candidate, parses cleanly. [apply (mulrC<:int poly>)] now discharges directly. Smoke test ([test_mulrC_at_int_poly], [test_mulrA_at_int_poly]) covers both. Validates: tcalgebra suite (TcMonoid/TcRing/TcInt/TcBigop/TcBigalg/ TcNumber/TcPoly/TcPolySmokeTest) all still pass; [+]/[-]/[**] paths unaffected (their candidate sets don't trigger the new pass).
1 parent 1c087d0 commit c47657a

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

src/ecTyping.ml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,60 @@ let gen_select_op
494494
not (has_tc_op_with_name (EcPath.basename p))
495495
| _ -> true) ops in
496496

497+
(* [drop_subsumed_tc] classifies candidates by the [op_kind] of their
498+
declared path — but after typing, abbrev candidates are inlined and
499+
their bodies' heads are what actually appear in the elaborated
500+
term. So an abbrev whose body is itself a TC-op invocation
501+
(e.g. [TcMonoid.( * ) ['a <: mulmonoid] (x y) = (+)<:'a> x y])
502+
escapes [drop_subsumed_tc]'s filter even though, post-inline, it's
503+
a TC-op-headed term that may reduce to the same head as another
504+
candidate.
505+
506+
This pass closes that gap: for each candidate, compute its
507+
post-inline body head; collect the non-TC heads as
508+
[concrete_heads]; then drop any candidate whose post-inline head
509+
is a TC op that [tc_reduce]s to a head already in [concrete_heads].
510+
Mirror image of [drop_subsumed_tc] but operating on body heads
511+
rather than declared op_kind, catching the abbrev-to-TC-op case
512+
that the pre-inline classification misses. *)
513+
let drop_subsumed_by_post_inline_head ops =
514+
let is_tc_op p =
515+
match EcEnv.Op.by_path_opt p env with
516+
| Some { op_kind = OB_oper (Some (OP_TC _)) } -> true
517+
| _ -> false in
518+
let body_head ((path, etyargs), _, _, bd) =
519+
match bd with
520+
| None -> Some (path, etyargs)
521+
| Some bd_lazy ->
522+
let _, body = Lazy.force bd_lazy in
523+
let head, _ = EcTypes.destr_app body in
524+
(match head.e_node with
525+
| Eop (p, tys) -> Some (p, tys)
526+
| _ -> None) in
527+
let concrete_heads =
528+
List.filter_map (fun cand ->
529+
match body_head cand with
530+
| Some (p, _) when not (is_tc_op p) -> Some p
531+
| _ -> None) ops in
532+
if concrete_heads = [] then ops
533+
else
534+
List.filter (fun cand ->
535+
match body_head cand with
536+
| Some (p, etyargs) when is_tc_op p -> begin
537+
match EcEnv.Op.tc_reduce env p etyargs with
538+
| red ->
539+
let red_head =
540+
match red.f_node with
541+
| Fop (p', _) -> Some p'
542+
| Fapp ({ f_node = Fop (p', _) }, _) -> Some p'
543+
| _ -> None in
544+
(match red_head with
545+
| None -> true
546+
| Some p' -> not (List.exists (EcPath.p_equal p') concrete_heads))
547+
| exception EcEnv.NotReducible -> true
548+
end
549+
| _ -> true) ops in
550+
497551
(* Drop a TC-bounded notation candidate (an abbrev whose tparams have
498552
non-empty TC bounds, e.g. [TcRing.(-) ['a <: addgroup] (x y) = …])
499553
when a same-basename candidate with no TC-bounded tparams (e.g. the
@@ -538,6 +592,9 @@ let gen_select_op
538592
let ops =
539593
let pruned = drop_tc_bounded_notation ops in
540594
if pruned = [] then ops else pruned in
595+
let ops =
596+
let pruned = drop_subsumed_by_post_inline_head ops in
597+
if pruned = [] then ops else pruned in
541598
(List.map fop ops)
542599

543600
and pvs () : OpSelect.gopsel list =

0 commit comments

Comments
 (0)