[asl] maintain a type annotation for every typechecked expression#1870
[asl] maintain a type annotation for every typechecked expression#1870Roman-Manevich wants to merge 1 commit into
Conversation
|
|
||
| let integer_exact' e = well_constrained' [ Constraint_Exact e ] | ||
| let integer_exact' e = | ||
| well_constrained' [ Constraint_Exact (e |> with_integer_ty) ] |
There was a problem hiding this comment.
Is this right? I'm a bit surprised that this is editing the type of e
There was a problem hiding this comment.
This replaces e with a copy that has the integer type as its type annotation
There was a problem hiding this comment.
Would we not expect e to be type-annotated already? If not, does the integer type annotation add anything?
|
|
||
| let qualifier_equal (q1 : func_qualifier option) q2 = Option.equal ( = ) q1 q2 | ||
| let var_ x = E_Var x |> add_dummy_annotation | ||
| let int_expr_of_var x = E_Var x |> add_dummy_annotation |> with_integer_ty |
There was a problem hiding this comment.
In general, the usage of with_integer_ty seems suspect to me - how do we know whether it should be (un)constrained, parameterised? I think we should be relying on type-checking to fill this in wherever possible, and avoiding overwriting types as we do several times in this file
There was a problem hiding this comment.
The is needed for the cases where we are constructing typed AST expressions without later annotating them. In such cases, we need to establish the types ourselves.
There was a problem hiding this comment.
I understand that we may not have annotated these, but how can we know that integer is the right type?
There was a problem hiding this comment.
Note that it is possible that we could type-check those expressions, even if it's a bit useless.
| List.map plug_one | ||
|
|
||
| (** Checks that all expressions have [opt_type = Some t] *) | ||
| module CheckTypeAnnotations = struct |
There was a problem hiding this comment.
Why are we performing this check? This is just additional information, it's not mandated?
There was a problem hiding this comment.
I used it for testing and then turned it off. We can probably remove it, but we may want to preserve it until we have more validation by some actual use cases.
There was a problem hiding this comment.
I think we should remove for the merge of this PR.
we may want to preserve it until we have more validation by some actual use cases.
I think the opposite actually - this is speculating a need for exhaustivity that we don't yet have supported by usage. I suggest we start with a basic level of annotation and expand as necessary
a8097cc to
124b7b9
Compare
HadrienRenaud
left a comment
There was a problem hiding this comment.
I think this needs to be more carefully documented. For example one of the important thing that is not said is that none of those type informations are used for type-checking.
Another important choice made here is not to use dependent types for some constructed expressions. This needs to be justified. I can see other options:
- Do not put any type annotations for those.
- Put only integer, as is done here
- Use recursive values to provide full precision types for literal values (this seems dangerous to me).
Maybe this should be hidden behind a command line flag not to impact the reference interpreter.
In the long term, we should probably write a typed AST which would incorporate those informations, and document what precision is given by those.
| type 'a annotated = { | ||
| desc : 'a; | ||
| pos_start : position; | ||
| pos_end : position; | ||
| version : version; | ||
| opt_type : ty option; | ||
| } |
There was a problem hiding this comment.
I would keep it separated with the following:
| type 'a annotated = { | |
| desc : 'a; | |
| pos_start : position; | |
| pos_end : position; | |
| version : version; | |
| opt_type : ty option; | |
| } | |
| type ('term, 'ty) annotated = { | |
| desc : 'term; | |
| pos_start : position; | |
| pos_end : position; | |
| version : version; | |
| opt_type : 'ty option; | |
| } |
And then declare expr as (expr_desc, ty) annotated, while for example stmt could be (stmt_desc, unit) annotated
|
|
||
| let integer_exact' e = well_constrained' [ Constraint_Exact e ] | ||
| let integer_exact' e = | ||
| well_constrained' [ Constraint_Exact (e |> with_integer_ty) ] |
There was a problem hiding this comment.
Why this? At the very least, the choice of typing those as integer needs to be documented and justified. Also what needs to be justified is that this does not break anything, as now a naive implementation of type_of would find those of type integer and not integer {e}, and maybe this breaks the invariant that all expressions in types are bounded.
IMHO if it is possible, we would leave those without a type.
Naive implementation of type_of for reference.
let type_of e =
match e.type_opt with
| Some t -> t
| None -> Typing.annotate_expr e |> sndNote that here type_of one_expr != Typing.annotate_expr one_expr |> snd
There was a problem hiding this comment.
IMHO if it is possible, we would leave those without a type.
Agreed with this - I think any type annotations we introduce in the first instance should be coming out of the existing type-checking
|
|
||
| let primitives = | ||
| let e_var x = E_Var x |> add_dummy_annotation in | ||
| let e_int_var x = E_Var x |> add_dummy_annotation |> with_integer_ty in |
There was a problem hiding this comment.
This also needs to be justified.
There was a problem hiding this comment.
I'm not sure in what sense this needs to be justified as this is a construction function. Each of the usages indeed requires an integer-typed variable (for bitwidth, for an integer exponentiation operation, and for the integer variable N.
There was a problem hiding this comment.
However it doesn't justify why this shouldn't be a constrained or parameterised integer. For example I see it used in a T_Bits, which usually needs a constrained or parameterised integer right?
| and annotate_expr env (e : expr) : ty * expr * SES.t = | ||
| let t, e', ses = annotate_expr_impl env e in | ||
| let e_with_type = { e' with opt_type = Some t } in | ||
| (t, e_with_type, ses) | ||
|
|
||
| (** [annotate_expr_impl] annotates the expression [e] in [env] without setting | ||
| a type annotation. *) | ||
| and annotate_expr_impl env (e : expr) : ty * expr * SES.t = | ||
| let () = if false then Format.eprintf "@[Annotating %a@]@." PP.pp_expr e in | ||
| let here x = add_pos_from ~loc:e x and loc = to_pos e in | ||
| match e.desc with |
There was a problem hiding this comment.
You can avoid putting a new function in this very recursive definition with the following:
| and annotate_expr env (e : expr) : ty * expr * SES.t = | |
| let t, e', ses = annotate_expr_impl env e in | |
| let e_with_type = { e' with opt_type = Some t } in | |
| (t, e_with_type, ses) | |
| (** [annotate_expr_impl] annotates the expression [e] in [env] without setting | |
| a type annotation. *) | |
| and annotate_expr_impl env (e : expr) : ty * expr * SES.t = | |
| let () = if false then Format.eprintf "@[Annotating %a@]@." PP.pp_expr e in | |
| let here x = add_pos_from ~loc:e x and loc = to_pos e in | |
| match e.desc with | |
| (** [annotate_expr] annotates the expression [e] in [env]. *) | |
| and annotate_expr env (e : expr) : ty * expr * SES.t = | |
| let () = if false then Format.eprintf "@[Annotating %a@]@." PP.pp_expr e in | |
| let here x = add_pos_from ~loc:e x and loc = to_pos e in | |
| set_type3 | |
| @@ | |
| match e.desc with |
Maybe in ASTUtils.ml
let set_type3: 'ty * ('val, 'ty) annotated * 'data -> 'ty * ('val, 'ty) annotated * 'data =
fun (t, e, d) -> (t, with_ty_annot t e, d)There was a problem hiding this comment.
Thanks. I went with set_expr_type_annotation instead of set_type3.
| If both arguments are physically equal, then the result is also physically | ||
| equal. *) | ||
|
|
||
| val with_ty_annot : AST.ty -> 'a annotated -> 'a annotated |
There was a problem hiding this comment.
With my proposal for the definition of annotated, it can have a more flexible definition, without losing the type constraints:
| val with_ty_annot : AST.ty -> 'a annotated -> 'a annotated | |
| val with_ty_annot : 'ty -> ('a, 'ty) annotated -> ('a, 'ty) annotated |
(with a slight change in how with_ty_annot is written, we could also transform it to 'ty -> ('a, 'b) annotated -> ('a, 'ty) annotated)
| match C.check with | ||
| | TypeCheck -> | ||
| false (* set to true if debugging missing annotations is needed *) | ||
| | _ -> false |
There was a problem hiding this comment.
Can you not add a command line flag for this? I'm wondering if adding the type information should also be a command line flag.
124b7b9 to
df82466
Compare
There was a problem hiding this comment.
I think we need to reduce the scope of this PR. I think it should contain only:
- Mechanism for storing type annotations - whether the existing version or Hadrien's proposal.
- Storing type annotations as calculated during type-checking already (i.e. wrap
annotate_{expr,lexpr},base_value)
I.e. the additional type annotations not provided by the type system, the exhaustivity checks should be removed. In future we can discuss whether we consider "missing" type annotations as truly missing (and if so whether they should be inserted post-hoc or the type system should be changed to calculate them appropriately), and how this could all be encoded into a truly typed AST.
Please let me know if you disagree.
This PR enables maintaining the type inferred for each expression during typechecking as an annotation attached to the expression. The intention is to make this available for tools built on top of
aslref.