Skip to content

Conversation

@DCupello1
Copy link

@DCupello1 DCupello1 commented Nov 12, 2025

This pass expands the subtyping patterns that appear in the LHS of
function clauses and type family arguments.

It achieves this through the following steps:

  • For each argument, we collect every unique sub expression.
  • Then, for each sub expression, we collect every case that is
    possible in the subtype. If the specific case additionally carries
    values, then we generate binds to add in the function scope.
  • With all of these cases, for each unique sub expression, we compute
    the cartesian product in order to absolutely grab all the possible cases.
    See $cvtop to see how this might be done.
  • Once we have calculated the product, we generate a subst for each product
    and proceed to generate the clause/type instance.
  • Finally, we filter out binds that refer to the subst ids.

For example, take the following types and function:

  syntax A = t1 nat | t2 nat nat
  syntax B = t1 nat | t2 nat nat | t3 | t4 

  def $foo(B) : nat
  def $foo(x : A <: B) = 1
  def $foo(t3) = 2
  def $foo(t4) = 3

Would be transformed as such:

  def $foo(B) : nat
  def $foo{n : nat}(t1(n)) = 1
  def $foo{n1 : nat, n2 : nat}(t2(n1, n2)) = 1
  def $foo(t3) = 2
  def $foo(t4) = 3

To give a slightly more involved example:

  syntax A = t1 | t2
  syntax B = t1 | t2

  def $foo(B) : nat
  def $foo(x : A <: B, y : A <: B) = 1
  def $foo(t3) = 2
  def $foo(t4) = 3

Would be transformed as such:

  syntax A = t1 | t2
  syntax B = t1 | t2 | t3

  def $foo(B) : nat
  def $foo(t1, t1) = 1
  def $foo(t2, t1) = 1
  def $foo(t1, t2) = 1
  def $foo(t2, t2) = 1
  def $foo(t3) = 2
  def $foo(t4) = 3

Currently it has a similar limitation as the sub pass: if you expand type families, then the evaluator (which the validator uses) cannot show that types are in fact equivalent.

Currently a draft pull request until the rework of middlend testing goes through.

@nomeata
Copy link

nomeata commented Nov 12, 2025

Nice!

Once we have calculated the product, we generate a subst for each product
and proceed to generate the clause/type instance.
Finally, we filter out binds that do not appear in the substed rhs.

What would be an example for a binding that can be removed that was not already in the domain of the generated substitution?

@DCupello1
Copy link
Author

What would be an example for a binding that can be removed that was not already in the domain of the generated substitution?

In the previous implementation, I made it so that the case expressions generated from the subtype would also generate the binds as well if necessary.

So take this example (as above):

  def $foo(B) : nat
  def $foo{n : nat}(t1(n)) = 1
  def $foo{n1 : nat, n2 : nat}(t2(n1, n2)) = 1
  def $foo(t3) = 2
  def $foo(t4) = 3

However, to make it simple, I would collect all the binds of all of the case expressions and apply it to all of them. So I would have [n, n1, n2] for both the first match and the second match. To mitigate this, I filter any binds that don't appear in in the rhs and premises.

I didn't like this approach, so I have changed it so that the case expressions actually carry the binds with them as well. This means I only filter at the end now to remove the subst ids (in the case above it would be x)

@DCupello1 DCupello1 marked this pull request as ready for review November 13, 2025 11:33
@DCupello1
Copy link
Author

Should be complete now! Though will need the uncase PR (#191) to be merged first.

This was referenced Nov 13, 2025
@nomeata
Copy link

nomeata commented Nov 13, 2025

This is looking good, for example I get

 ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
 def $zsize(storagetype : storagetype) : nat
   ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
-  def $zsize{numtype : numtype}((numtype : numtype <: storagetype)) = $size(numtype)
+  def $zsize(I32_storagetype) = $size(I32_numtype)
+  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
+  def $zsize(I64_storagetype) = $size(I64_numtype)
+  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
+  def $zsize(F32_storagetype) = $size(F32_numtype)
   ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
-  def $zsize{vectype : vectype}((vectype : vectype <: storagetype)) = $vsize(vectype)
+  def $zsize(F64_storagetype) = $size(F64_numtype)
   ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
-  def $zsize{packtype : packtype}((packtype : packtype <: storagetype)) = $psize(packtype)
+  def $zsize(V128_storagetype) = $vsize(V128_vectype)
+  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
+  def $zsize(I8_storagetype) = $psize(I8_packtype)
+  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
+  def $zsize(I16_storagetype) = $psize(I16_packtype)

but with

syntax storagetype = 
  | BOT
  | I32
  | I64
  | F32
  | F64
  | V128
  | REF{`null?` : null?, heaptype : heaptype}(null?{null <- `null?`} : null?, heaptype : heaptype)
  | I8
  | I16

this is now partial…

ah, but it seems this was already the case in the source, which has

syntax storagetype hint(desc "storage type") hint(prefix "STORAGETYPE_") = valtype | packtype

def $zsize(storagetype) : nat  hint(show |%|)
def $zsize(numtype) = $size(numtype)
def $zsize(vectype) = $vsize(vectype)
def $zsize(packtype) = $psize(packtype)

Is that just a case of a missing hint(partial)?

@nomeata
Copy link

nomeata commented Nov 13, 2025

For cunpack I get overlapping patterns with superficially conflicting definitions

;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
def $cunpack(storagetype : storagetype) : consttype?
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I32_storagetype) = ?(I32_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I64_storagetype) = ?(I64_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(F32_storagetype) = ?(F32_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(F64_storagetype) = ?(F64_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(V128_storagetype) = ?(V128_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I8_storagetype) = ?(I32_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I16_storagetype) = ?(I32_consttype)
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I32_storagetype) = ?(($lunpack(I32_lanetype) : numtype <: consttype))
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I64_storagetype) = ?(($lunpack(I64_lanetype) : numtype <: consttype))
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(F32_storagetype) = ?(($lunpack(F32_lanetype) : numtype <: consttype))
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(F64_storagetype) = ?(($lunpack(F64_lanetype) : numtype <: consttype))
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I8_storagetype) = ?(($lunpack(I8_lanetype) : numtype <: consttype))
  ;; ../../../../specification/wasm-3.0/1.2-syntax.types.spectec
  def $cunpack(I16_storagetype) = ?(($lunpack(I16_lanetype) : numtype <: consttype))
  def $cunpack{x0 : storagetype}(x0) = ?()

@DCupello1
Copy link
Author

Is that just a case of a missing hint(partial)?

That seems to be the case I would say.

For cunpack I get overlapping patterns with superficially conflicting definitions

I do not handle overlapping patterns, since it might not be necessary to remove them for some targets. I could remove them easily if necessary though

@nomeata
Copy link

nomeata commented Nov 13, 2025

I do not handle overlapping patterns, since it might not be necessary to remove them for some targets. I could remove them easily if necessary though

At least the lean4 backend will need that. But that doesn’t mean you have to do that right away, I can also do it at some point.

@DCupello1
Copy link
Author

At least the lean4 backend will need that. But that doesn’t mean you have to do that right away, I can also do it at some point.

Rocq will also need it so will probably do it after this gets merged, either as part of this pass or a new pass.

@nomeata
Copy link

nomeata commented Nov 14, 2025

Rocq will also need it so will probably do it after this gets merged, either as part of this pass or a new pass.

Actually Lean has set_option match.ignoreUnusedAlts false so it’s not blocking me.

@DCupello1 DCupello1 mentioned this pull request Nov 20, 2025
4 tasks
Comment on lines +72 to +75
let rec collect_sube_exp e =
let c_func = collect_sube_exp in
match e.it with
(* Assumption - nested sub expressions do not exist. Must also be a varE. *)
Copy link

Choose a reason for hiding this comment

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

You used the Iter module in #191, any reason to not use it here?

Copy link
Author

Choose a reason for hiding this comment

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

No real reason, probably was trying to get a more functional approach instead of using Iter.

Also since its only traversing through exp I maybe thought it was overkill.

Copy link

Choose a reason for hiding this comment

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

Ah, good point. No issue, was just curious.

I wish we didn't have to duplicate the traversal logic in every pass, but 🤷🏻

Copy link
Author

Choose a reason for hiding this comment

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

Yeah... I really wish there was an easier way to make a generic traversal pass that didn't involve side effects 😅 . I'll look into it more to see if something can be done

@DCupello1 DCupello1 merged commit 0308a06 into main Dec 4, 2025
9 checks passed
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.

3 participants