Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for naming scrutinee in dependent pattern matching #2464

Merged
merged 33 commits into from
Feb 23, 2022

Conversation

aseemr
Copy link
Collaborator

@aseemr aseemr commented Feb 22, 2022

Dependent pattern matching in F*

PR #2256 added support for dependent pattern matching with returns annotations on match. E.g., from ulib/Steel.ST.EphemeralHashtbl.fst:

let vopt = Seq.index s (U32.v idx) in
match vopt returns STGhostT _ _ _ (fun _ -> tperm a (Map.upd m i c) (Map.remove borrows i)) with ...

However, it comes with a restriction that the scrutinee expression has to be a name in the presence of a returns annotation. As a result, the snippet above has to let-bind the scrutinee expression.

This PR

This PR adds support for naming the scrutinee using as keyword similar to Coq. E.g. the code above can now be written as:

match Seq.index s (U32.v idx) as y returns STGhostT _ _ _ (fun _ -> tperm a (Map.upd m i c) (Map.remove borrows i)) with ...

where y may appear free in the type/computation type after returns.

As an optimization, note that the computation type after returns in the first snippet does not contain vopt, so in this case, we can even write:

match Seq.index s (U32.v idx) returns STGhostT _ _ _ (fun _ -> tperm a (Map.upd m i c) (Map.remove borrows i)) with ...

thereby eliminating the unnecessary naming of the scrutinee.

Code changes

AST, syntax, desugaring, resugaring, reflection

In the parser AST, a returns annotation may now optionally have a ident. Whereas in the core syntax, the returns annotation is of the form (b, asc), where b is a binder and asc is an Syntax.ascription node.

Parser AST returns annotation (x, t) desugars to (x_b, asc), where x_b is a binder for x and t desugars to asc, may be containing the bound variable x_b.

On the other hand, parser AST returns annotation t desugars to (uu___ret_, asc), i.e. we introduce a new binder. One subtlety here is that, it may be the case that source program does not name the scrutinee, but scrutinee is already a name and occurs free in the returns annotation, e.g.:

match x returns Tot (n:int{n > x}) with ...

In this case, when we introduce a new binder uu___ret_, we also substitute x in t with uu___ret_.

That way, core typechecker uniformly works with an explicit binder for the returns annotation.

When resugaring, we check that if the binder name for the returns annotation is the internal name uu___ret_, we do not print it, and appropriately substitute the scrutinee back if necessary. See https://github.com/FStarLang/FStar/blob/aseem_dependent_pattern_matching2/tests/prettyprinting/Match.Returns.fst and https://github.com/FStarLang/FStar/blob/aseem_dependent_pattern_matching2/tests/prettyprinting/Match.Returns.fst.expected for pretty-printing testcases.

Reflection AST, embeddings, and unembeddings are also fixed accordingly.

Typechecker

To typecheck

match e as x returns C with
| P_i -> e_i

We first typecheck G |- e : C_e. Then we typecheck G, x : C_e.t |- C : _, i.e. extend the environment with x:t where t is the type of e and typecheck C. Then we ascribe each branch as e_i <: C[P_i/x], and typecheck the ascribed branch. The final type of the match result is computed as C[e/x] (as usual we later bind it with C_e).


TcUtil.bind_cases env t cases guard_x, g, erasable
//c has b free, so substitute it with the scrutinee
let c = SS.subst_comp [NT (b.binder_bv, e1)] c in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is where we compute the type of the match result by substituting binder with e1.

let bs, asc = SS.open_ascription [b] asc in
let b = List.hd bs in
//we set the sort of the binder to be the type of e1
{b with binder_bv={b.binder_bv with sort=c1.res_typ}}, asc in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adding the returns binder with sort as the type of e1, to typecheck the ascription.

| Some (b, asc) ->
let asc = SS.close_ascription [b] asc in
let b = List.hd (SS.close_binders [b]) in
let b = {b with binder_bv={b.binder_bv with sort=tun}} in
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

While packing the returns annotation back, set the sort to _. So we don't have to normalize it, instantiate it, compute its fvs etc. etc.

Copy link
Collaborator

Choose a reason for hiding this comment

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

interesting ... nice trick

asc
|> SS.subst_ascription [NT (scrutinee, norm_pat_exp)]
|> SS.subst_ascription [NT (b.binder_bv, norm_pat_exp)]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ascribe the branches before checking them.

@@ -173,6 +173,7 @@ let keywords =
ALWAYS, "let" ,LET(false);
ALWAYS, "logic" ,LOGIC;
ALWAYS, "match" ,MATCH;
ALWAYS, "as" ,AS;
Copy link
Collaborator

Choose a reason for hiding this comment

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

super tiny nit, sorry. But this list of keywords was originally meant to be in alphabetical order. I see there are some deviations from that already, but may be worth restoring at some point.

((p_noSeqTermAndComment false false e) ^/+^
(match as_opt with
| None -> empty
| Some as_ident -> str " as" ^/+^ (p_ident as_ident)) ^/+^
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need the preceding " " in as?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for noticing! I removed these extra spaces and updated the pretty printing tests.

(str "ret" ^/+^ p_tmIff ret) ^/^
((match as_opt with
| None -> empty
| Some as_ident -> str " as " ^/^ p_ident as_ident ^/^ str " ")
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as below ... do we need the spaces around it?

@@ -1,22 +1,32 @@
module Match.Returns

let test (n: nat) =
match n returns nat with
match n returns nat with
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if this extra space will disappear with some tuning in ToDocument

match ret_opt with
| None -> ""
| Some (b, asc) ->
(binder_to_string b ^ " ")
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing an as here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is simply printing the AST, e.g. Tm_match is already printed as (scrutinee, branches), so I followed the same style (e.g. no returns as well).

@@ -78,12 +78,12 @@ let indexing' (#a #b: _) (v:view a b) (len_as:nat) (i:nat{i < len_as * View?.n v
let indexing #b vb i = indexing' (get_view vb) (B.length (as_buffer vb)) i

let sel' (#a #b: _) (v:view a b)
(as:Seq.seq a)
(i:nat{i / View?.n v < Seq.length as})
(es:Seq.seq a)
Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks for all the fixes in this file!

_
_
(fun _ -> tperm a (Map.upd m i c) (Map.remove borrows i)) with
match Seq.index s (U32.v idx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice

@nikswamy
Copy link
Collaborator

Thanks Aseem.. This looks great! Thanks for all the explanations and code comments in TcTerm too.

I left a few minor nit-picky comments.

@aseemr
Copy link
Collaborator Author

aseemr commented Feb 23, 2022

Thanks for the review Nik! I incorporated the comments, except the alphabetical sorting of keywords in the lexer(s). I will raise an enhancement issue for that.

@aseemr aseemr merged commit c231357 into master Feb 23, 2022
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