Visitar URL original
Decide whether pi is large based on original codomain sort by NathanielB123 · Pull Request #8870 · agda/agda · GitHub
Skip to content

Decide whether pi is large based on original codomain sort - #8870

Merged
andreasabel merged 16 commits into
agda:masterfrom
NathanielB123:block-pisort
Oct 26, 2026
Merged

andreasabel merged 16 commits into
agda:masterfrom
NathanielB123:block-pisort

Conversation

@NathanielB123

@NathanielB123 NathanielB123 commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Fixes #5810 and #8867

The main idea behind this fix is from @jespercockx (#5810 (comment))

Every PiSort represents a postponed problem of whether the pi type should be level-dependent or not. This dependency check should be performed on the original codomain sort (in its original context) as opposed to the current codomain because dependency is not stable under substitution.

Unfortunately, getting access to the original codomain's context seems challenging. telePi and abstract can both create new pi types AND are both pure functions. We therefore instead build a "large enough" context filled with __DUMMY_DOM__s. This is very ugly (and probably breaks in some edge cases, e.g. with local rewrite rules), but the alternative would be a major refactor.

Note that after this change, telView followed by abstract is no longer identity: abstract creates new pi types and therefore new dependency problems. A more principled approach would be to track level-dependency in Doms, but this seems more challenging, and in fact would be a breaking change: module parameters can currently be regarded as both level-dependent and not level-dependent simultaneously, depending on how they are used in module definitions (#5810 (comment)).

@NathanielB123
NathanielB123 marked this pull request as ready for review October 23, 2026 07:43
@NathanielB123 NathanielB123 changed the title DRAFT: Decide whether pi is large based on original codomain sort Decide whether pi is large based on original codomain sort Oct 23, 2026
@andreasabel andreasabel added sorts Agda's sort system (see also piSort); univSort; Sort metas; Fibrancy piSort Caused by or relating to the treatment of Π type sorts labels Oct 24, 2026
@andreasabel andreasabel added this to the 2.9.0 milestone Oct 24, 2026

@andreasabel andreasabel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

There is this hack with creating a large enough context.
So is the new BlockedDepCheck not always living in the ambient context?
The documentation of this component should then state this (resp., state in which context this component is supposed to live).

Comment thread src/full/Agda/Syntax/Internal.hs Resolved
Comment thread src/full/Agda/TypeChecking/Reduce.hs Resolved
@NathanielB123
NathanielB123 force-pushed the block-pisort branch 3 times, most recently from 7393811 to 611418d Compare October 24, 2026 08:31
@andreasabel

Copy link
Copy Markdown
Member

Since this is supposed to go on 2.9.0, I asked Claude for a review, and it found two regressions, one internal error, and one case with with. I paste the review here. This should be investigated before merging, and the reproducers should go into the testsuite to guard against regressions.

Regression 1: internal error with rewrite rules in a parameterised module. Master accepts this file; the PR dies with __IMPOSSIBLE__ at Reduce.hs:488.

{-# OPTIONS --rewriting #-}
open import Agda.Primitive
open import Agda.Builtin.Equality
open import Agda.Builtin.Equality.Rewrite

postulate
  I : Set
  g : Level → I → Level

module _ (a : Level) where
  postulate p : ∀ {i} → g a i ≡ lzero
  {-# REWRITE p #-}

  postulate
    F : (b : Level) (i : I) → Set (g b i)
    G : (b : Level) (X Y : (i : I) → Set (g b i)) → Set

  test : (b : Level) → G b (F b) (F b) ≡ G b (F b) (λ i → F b i)
  test b = refl

Cause: inLargeEnoughContext uses inTopContext, which drops the module checkpoints. Inside the dummy context moduleParamsToApply then returns no arguments (see its comment about #4383), so the rule is used uninstantiated as g ?a i ↦ lzero and rewrites g b i. In the real context the rule is instantiated to g a i and does not fire. So the old codomain sort comes out NotFree while the current one is MaybeFree, which the code asserts cannot happen. The same dummy-context trick also loses local rewrite rules (--local-rewriting) and let-bindings, so the "old" dependency check can disagree with the real context in the other direction too. I could not turn the local-rewriting case into a user-visible failure, but it is the same root cause.

Regression 2: with on a term whose type went through substitution. Master accepts this; the PR rejects it.

open import Agda.Primitive

postulate
  I : Set
  F : (o : I → Level) (i : I) → Set (o i)

test : Set₁
test with F (λ _ → lzero)
... | _ = Set
error: [UnequalTypes]
The types Set₂ and Setω are not equal
when checking that the type ((i : I) → Set) → Set₁ of the generated
with function is well-formed

The same happens with module M (o : I → Level) where postulate F : (i : I) → Set (o i) followed by open M (λ _ → lzero) and with F. Cause: after piApply, the type of F (λ _ → lzero) keeps PiSort (CodomainSortOld (Set (lsuc (o i)))), which now reduces to Setω by design. But sortOf (used by checkType on the with-function type, checkSolutionSort, checkInternal) recomputes sorts from the term and gets Set₁. The debug output shows exactly this: "checking if sort Set₂ of pi type ((i : I) → Set) → Set₁ fits in given sort Setω". So stored sorts and recomputed sorts are no longer consistent, and every place that compares them with CmpEq can fail. with is the one I could trigger; --double-check and reflection's checkType are candidates for the same mismatch.

Smaller remarks

  • simplify' for sorts does not instantiate metas before piSortB', so a CodomainSortOld holding an instantiated sort meta while the current codomain is already Setω would hit the (_, Right LargeSort{}) impossible case in piSortB''. I did not find a trigger; it is reachable only via interactive "simplified" normalisation.
  • Derived Eq/SynEq on PiSort now compares the stored old sort, so two otherwise identical PiSorts fail syntactic equality and get postponed in piSortEquals until their blockers clear. Harmless in all tests, but it is extra constraint churn.
  • Several traversals (Free, Occurs, positivity, termination) deliberately skip the old sort. That is fine as long as nothing interprets it in a real context, which currently holds.

@andreasabel andreasabel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please investigate the reproducers found by AI.

@NathanielB123

NathanielB123 commented Oct 25, 2026 •

Copy link
Copy Markdown
Member Author

That should be both regressions fixed.

I am less convinced by Claude's "smaller remarks":

  • I don't see how the current codomain sort could ever become a large sort if the original codomain sort was dependent (and therefore not large)
  • We definitely cannot ignore the blocked dependency check in while checking syntactic equality: the blocked dependency check can determine whether the pi reduces to a large or small sort, and these are not equal!
  • Skipping the codomain sort in those traversals is intentional

There definitely could be bugs somewhere related to these things which I have missed, and obviously the reproducers were useful, but I'm not convinced sending LLM generated comments like these without validation is helpful... I spent quite a while thinking about the first remark before deciding it wasn't a bug

@andreasabel

Copy link
Copy Markdown
Member

Thanks!

Well, you are the expert on your PR. You can ignore the speculations if you find they have no substance. I am in a worse position to judge whether there is anything to it or not.

@andreasabel andreasabel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update.

@andreasabel
andreasabel merged commit 7d13ad7 into agda:master Oct 26, 2026
22 checks passed
andreasabel added a commit that referenced this pull request Oct 28, 2026
* New PiSort field

* I am going insane

* Stuff

* Subject reduction fix?

* Update reduction for PiSort

* Fixes

* Fixes

* Optimisations

* Small fix

* Clean up context handling

* Micro-optimisation

* Cleanup

* Comments/cleanup

* Relax double checking of pi sorts

* Disable rewrite rules when checking old pi codom

---------

Co-authored-by: Andreas Abel <andreas.abel@ifi.lmu.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

piSort Caused by or relating to the treatment of Π type sorts pr: squash-me This PR needs squashing sorts Agda's sort system (see also piSort); univSort; Sort metas; Fibrancy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Loss of subject reduction with Setω Regression in 2.9.0-rc1: instance candidate that cannot match is never discarded (mismatch postponed at a blocked piSort)

3 participants