[aslspec] type parameter refactoring and type inference - #1927
[aslspec] type parameter refactoring and type inference#1927Roman-Manevich wants to merge 3 commits into
Conversation
Resolve operator type-parameter references to explicit Parameter nodes, matching parameterized typedefs. This removes the need to inject synthetic parameter types into per-operator symbol tables.
Separate type-parameter inference and substitution from operator instantiation, and describe diagnostics in terms of a generic owner. This prepares the machinery for reuse by parameterized typedefs without changing accepted specifications.
Instantiate parameterized variants while checking constructors, field access and updates, list indexing, and function application. Add successful end-to-end coverage and a diagnostic for inconsistent parameter bindings.
HadrienRenaud
left a comment
There was a problem hiding this comment.
In general, this looks good to me.
I struggle a bit to see the big picture, so consider maintaining a big picture explanation of what is happening.
Detects duplicate operator type parameters.
I think having a small example in the PR description would make it clearer what this is about
I think it is worth considering implementing a map_terms function on your types so that you can implement term rewriting more easily.
|
|
||
| typing relation make_leaf(x: Int) -> (Tree[[Int]]) | ||
| { | ||
| prose_transition = "" |
There was a problem hiding this comment.
Can we make prose_transition optional?
| in | ||
| () |
There was a problem hiding this comment.
Consider using |> ignore instead of let _ = ... in () to decrease indentation and simplify code.
| if StringSet.mem parameter seen then | ||
| Error.duplicate_type_parameter loc ~owner:("operator " ^ name) | ||
| parameter | ||
| else StringSet.add parameter seen) |
There was a problem hiding this comment.
optionally: avoid doing 2 accesses by checking physical equality:
let seen' = StringSet.add parameter seen in
if seen' == seen then
Error.duplicate_type_parameter loc ~owner:("operator " ^ name)
parameter;
seen'| check_well_instantiated spec term | ||
|
|
||
| let check ({ ast; id_to_defining_node } as spec) = | ||
| let check ({ ast; _ } as spec) = |
There was a problem hiding this comment.
At this point, do not deconstruct it here and use spec.ast at line 1772:
| let check ({ ast; _ } as spec) = | |
| let check spec = |
| (** Low-level, error-free transformations of explicit type parameters in type | ||
| terms. Parameter binding inference, which depends on type unification, is | ||
| implemented by [TypeInference.TypeParameterInference]. *) |
There was a problem hiding this comment.
If you could put a small example here that would help readability a lot
| (function Some count -> Some (count + 1) | None -> Some 1) | ||
| expanded_types | ||
|
|
||
| (** [subsumed id_to_defining_node expansion_limit expanded_types sub super] |
There was a problem hiding this comment.
this is outdated btw
| increment_expansion_count typename expanded_types | ||
| in | ||
| let variants = TypeParameterOps.variants spec typename argument in | ||
| (not (Utils.list_is_empty variants)) |
There was a problem hiding this comment.
why this line? can there be an empty list of variants? Why wouldn't they be subsumed then? the empty type could be understood as a subtype of anything (because it can't get any value anyway)
| String.equal sub_typename super_typename | ||
| && subsumed_rec spec expansion_limit expanded_types sub_term | ||
| super_term | ||
| | _, ParamType { typename; term = _, argument } -> |
There was a problem hiding this comment.
Can we add a negative test that is recursive and uses parameters?
| containing [variant]. Labelled variants are found through their label; | ||
| record variants are found through one of their globally unique fields. | ||
| *) | ||
| let containing_type_for_variant spec { TypeVariant.term; _ } = |
There was a problem hiding this comment.
If I'm reading this correctly, the only usage of containing_type_for_variant has an assert false if containing_type_for_variant return None.
Consider moving this assert false up the chain, making containing_type_for_variant return a 'a instead of a 'a option.
Benefits are simplifying the code , make it more succinct, and simplify debugging (multiple assert false give you more information when one of them is hit).
| match term with | ||
| | Term.Record { fields = { Term.name; _ } :: _; _ } -> | ||
| StringMap.find_opt name spec.field_to_containing_record | ||
| |> Option.map (fun { containing_type; _ } -> containing_type) | ||
| | Term.Label { label } | Term.Tuple { label_opt = Some label; _ } -> ( | ||
| match StringMap.find_opt label spec.variant_id_to_containing_type with | ||
| | Some type_name -> ( | ||
| match StringMap.find_opt type_name spec.id_to_defining_node with | ||
| | Some (Node_Type type_def) -> Some type_def | ||
| | _ -> None) | ||
| | None -> None) | ||
| | Term.Parameter _ | Term.TypeOperator _ | Term.ParamType _ | ||
| | Term.Tuple { label_opt = None; _ } | ||
| | Term.Record { fields = []; _ } | ||
| | Term.ConstantsSet _ | Term.Function _ -> | ||
| None |
There was a problem hiding this comment.
Suggestion above:
| match term with | |
| | Term.Record { fields = { Term.name; _ } :: _; _ } -> | |
| StringMap.find_opt name spec.field_to_containing_record | |
| |> Option.map (fun { containing_type; _ } -> containing_type) | |
| | Term.Label { label } | Term.Tuple { label_opt = Some label; _ } -> ( | |
| match StringMap.find_opt label spec.variant_id_to_containing_type with | |
| | Some type_name -> ( | |
| match StringMap.find_opt type_name spec.id_to_defining_node with | |
| | Some (Node_Type type_def) -> Some type_def | |
| | _ -> None) | |
| | None -> None) | |
| | Term.Parameter _ | Term.TypeOperator _ | Term.ParamType _ | |
| | Term.Tuple { label_opt = None; _ } | |
| | Term.Record { fields = []; _ } | |
| | Term.ConstantsSet _ | Term.Function _ -> | |
| None | |
| match term with | |
| | Term.Record { fields = { Term.name; _ } :: _; _ } -> | |
| let { containing_type; _ } = | |
| StringMap.find name spec.field_to_containing | |
| in | |
| containing_type | |
| | Term.Label { label } | Term.Tuple { label_opt = Some label; _ } -> ( | |
| let type_name = | |
| StringMap.find label spec.variant_id_to_containing_type | |
| in | |
| match StringMap.find type_name spec.id_to_defining_node with | |
| | Node_Type type_def -> type_def | |
| | _ -> assert false) | |
| | Term.Parameter _ | Term.TypeOperator _ | Term.ParamType _ | |
| | Term.Tuple { label_opt = None; _ } | |
| | Term.Record { fields = []; _ } | |
| | Term.ConstantsSet _ | Term.Function _ -> | |
| assert false |
This PR is the first in a sequence of changes to support grammar definitions in
aslspec(and also to model generic helper relations). Grammar definitions need parameterized relations to define reusable rules that build the ASL AST. A follow-up PR will unify operators and relations; this PR prepares for that by making type-parameter inference reusable and completing support for parameterized types.Parameterized types are currently not instantiated during type inference. For example:
When type checking
PairValue(x, y), we need to infer a concrete type forTfrom the types ofxandy, then instantiate the variantPairValuebefore comparing it with the expression.For example, if both
xandyhave typeInt,PairValue(x, y)is inferred asPair[[Int]], so accessingleftfrom aPair[[Int]]produces anInt. Ifxhas typeIntbutyhas typeBool, the two inferred types forTcannot be unified andaslspecreports an error.This PR: