Skip to content

SharedHole: let lowering state that two type positions are the same - #72

Merged
dpmills merged 2 commits into
dmills/freshen-levelfrom
dmills/shared-hole
Aug 12, 2026
Merged

SharedHole: let lowering state that two type positions are the same#72
dpmills merged 2 commits into
dmills/freshen-levelfrom
dmills/shared-hole

Conversation

@dpmills

@dpmills dpmills commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

A groupby's key type is its key function's codomain, and the lowered shape relates them nowhere: __gb_k's only occurrence is as an operand of the partition predicate's ==, so the key's type could only arrive backwards through that comparison — making group-by key inference depend on an operator's internals.

Type::SharedHole(id) lets lowering state the relation outright: a Hole with an identity, every occurrence of one id normalizing to the same inference variable. It is transient exactly as Hole is, and groups with it everywhere outside normalize_annotation.

groupby stamps one id on the key application and on its own data_fun annotation's domain — the positions the claim is about, not the binder this desugaring invented. That also makes the edge directional for free: bind_annotation records inferred <: ann and a function is contravariant in its domain, so keys flow into the domain rather than being forced equal to it.

symbolic does not render annotations, so test_lower_groupby cannot show the relation; test_groupby_key_type_comes_from_the_key_function pins it.

@dpmills
dpmills requested a review from a team as a code owner August 10, 2026 20:57
@dpmills
dpmills requested review from sortalongo and removed request for a team August 10, 2026 20:57
@dpmills
dpmills marked this pull request as draft August 10, 2026 20:57
@dpmills
dpmills force-pushed the dmills/shared-hole branch from faad609 to 0627ead Compare August 10, 2026 21:41
@dpmills
dpmills marked this pull request as ready for review August 10, 2026 21:50
Comment thread tests/type_check.rs
@dpmills
dpmills force-pushed the dmills/shared-hole branch from e5f32d2 to ab58b46 Compare August 11, 2026 23:36
A `groupby`'s key type *is* its key function's codomain, and the lowered shape relates them nowhere: `__gb_k`'s only occurrence is as an operand of the partition predicate's `==`, so the key's type could only arrive backwards through that comparison — making group-by key inference depend on an operator's internals.

`Type::SharedHole(id)` lets lowering state the relation outright: a `Hole` with an identity, every occurrence of one id normalizing to the same inference variable. It is transient exactly as `Hole` is, and groups with it everywhere outside `normalize_annotation`.

`groupby` stamps one id on the key application and on its own `data_fun` annotation's domain — the positions the claim is about, not the binder this desugaring invented. That also makes the edge directional for free: `bind_annotation` records `inferred <: ann` and a function is contravariant in its domain, so keys flow *into* the domain rather than being forced equal to it.

`symbolic` does not render annotations, so `test_lower_groupby` cannot show the relation; `test_groupby_key_type_comes_from_the_key_function` pins it.
Sharing is what the marker is for, so over-sharing is its characteristic failure, and it has two shapes. A `def` is lowered once, so its body carries one id however many times it is called — nothing about the marker keeps the instantiations apart, ordinary generalization does, and only because `normalize_annotation` resolves the id to a variable minted at the current level (the level caveat on `InferCtx::shared_holes` is exactly this). Separately, the id → variable memo lives on the inference context, so every group-by in a program shares one table and ids minted per construct have to stay distinct within a lowering.

`test_groupby_key_relation_is_per_occurrence` covers both: a polymorphic `def` wrapping `groupby`, called at an `Int` key and a `String` key, and two independent `groupby`s in one program. Either kind of over-sharing collapses the two key types into one variable, so neither shows up as a wrong key type — both reject the program with an `Int | String` collision. Confirmed by mutation: minting the shared variable at level 0 fails the `def` case, and returning a constant id from `fresh_shared_hole` fails the two-occurrence case.

`test_groupby_lookup_at_wrong_key_type_rejected` covers the other half. The tests above pin what a key type *resolves to*; stating the relation on the `data_fun` annotation also makes the edge directional (`key_ty <: ⟨domain⟩`, by contravariance), and a directional edge can go slack without any resolution test noticing — every other case here would still pass if a lookup at an unrelated key type were quietly accepted.
@dpmills
dpmills force-pushed the dmills/shared-hole branch from ab58b46 to 17c31a1 Compare August 12, 2026 00:09
@dpmills
dpmills merged commit 05f202e into main Aug 12, 2026
1 of 2 checks passed
@dpmills
dpmills deleted the dmills/shared-hole branch August 12, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants