Skip to content

Commit c3dff75

Browse files
Merge pull request herd#1738 from herd/aslspec-unlabelled-records
[aslspec] more precise support for unlabelled records
2 parents 40d085e + e9dd0f5 commit c3dff75

7 files changed

Lines changed: 114 additions & 58 deletions

File tree

asllib/aslspec/error.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ let invalid_number_of_components label expr ~expected ~actual =
150150
let invalid_record_field_names expr expr_field_names record_type_field_names =
151151
spec_error
152152
@@ Format.asprintf
153-
"The record expression %a has invalid field names: expected %s but \
154-
found %s"
153+
"The record expression %a has missing or invalid field names: expected \
154+
%s but found %s"
155155
PP.pp_expr expr
156-
(String.concat ", " expr_field_names)
157156
(String.concat ", " record_type_field_names)
157+
(String.concat ", " expr_field_names)
158158

159159
let non_field id expr =
160160
spec_error

asllib/aslspec/render.ml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -750,15 +750,15 @@ module Make (S : SPEC_VALUE) = struct
750750
formal_opt_prose_pairs
751751
in
752752
substitute expr_prose formal_prose_pairs
753-
| Record { label_opt = None; fields = _ } ->
754-
(* TODO: to obtain the prose for unlabelled records, we need a map from
755-
record fields to the type that contains the record. *)
756-
"<missing prose for unlabelled record>"
757-
| Record { label_opt = Some name; fields } ->
758-
let variant =
759-
match Spec.defining_node_for_id S.spec name with
760-
| Node_TypeVariant def -> def
761-
| _ -> assert false
753+
| Record { label_opt; fields } ->
754+
let variant = Spec.record_variant_for_expr S.spec expr in
755+
let name =
756+
match label_opt with
757+
| Some name -> name
758+
| None ->
759+
Format.asprintf "unlabelled record with fields [%a]"
760+
(PP.pp_sep_list ~sep:", " pp_print_string)
761+
(List.map fst fields)
762762
in
763763
let expr_prose =
764764
TypeVariant.prose_description variant

asllib/aslspec/spec.ml

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,36 @@ let make_variant_id_to_containing_type ast =
235235
acc_map variants)
236236
StringMap.empty typedefs
237237

238+
let make_field_to_containing_variant ast =
239+
let typedefs =
240+
List.filter_map (function Elem_Type def -> Some def | _ -> None) ast
241+
in
242+
List.fold_left
243+
(fun acc_map { Type.variants } ->
244+
List.fold_left
245+
(fun acc_map ({ TypeVariant.term } as variant) ->
246+
match term with
247+
| Term.Record { fields } ->
248+
let field_names = List.map (fun { Term.name } -> name) fields in
249+
List.fold_left
250+
(fun acc_map field_name ->
251+
StringMap.add field_name variant acc_map)
252+
acc_map field_names
253+
| _ -> acc_map)
254+
acc_map variants)
255+
StringMap.empty typedefs
256+
238257
type t = {
239258
ast : AST.t; (** The AST, added with builtin definitions, transformed. *)
240259
id_to_defining_node : definition_node StringMap.t;
241260
(** Associates identifiers with the AST nodes where they are defined. *)
242261
variant_id_to_containing_type : string StringMap.t;
243262
(** Associates variant labels with the name of the type that contains
244263
them. *)
264+
field_to_containing_variant : TypeVariant.t StringMap.t;
265+
(** Associates field names with the variant containing them. Since field
266+
names are unique, there is only one variant containing a given field.
267+
*)
245268
assign : Relation.t;
246269
reverse_assign : Relation.t;
247270
bottom_constant : Constant.t;
@@ -279,6 +302,7 @@ let update_spec_ast spec ast =
279302
ast;
280303
id_to_defining_node = make_symbol_table ast;
281304
variant_id_to_containing_type = make_variant_id_to_containing_type ast;
305+
field_to_containing_variant = make_field_to_containing_variant ast;
282306
}
283307

284308
let defined_ids self =
@@ -297,13 +321,26 @@ let defining_node_for_id self id =
297321
| Some def -> def
298322
| None -> Error.undefined_element id
299323

300-
(** [relation_for_id self id] returns the relation definition node for the given
301-
identifier [id], which is assumed to correspond to a relation definition. *)
302324
let relation_for_id self id =
303325
match defining_node_for_id self id with
304326
| Node_Relation def -> def
305327
| _ -> assert false
306328

329+
let record_variant_for_expr spec expr =
330+
match expr with
331+
| Expr.Record { fields } ->
332+
let first_field_name =
333+
match fields with
334+
| (field_name, _) :: _ -> field_name
335+
| _ -> failwith "Record expression must have a non-empty list of fields"
336+
in
337+
StringMap.find first_field_name spec.field_to_containing_variant
338+
| _ ->
339+
let msg =
340+
Format.asprintf "Expected record expression, found %a" PP.pp_expr expr
341+
in
342+
failwith msg
343+
307344
let is_defined_id self id = StringMap.mem id self.id_to_defining_node
308345
let elements self = self.ast
309346

@@ -2351,9 +2388,15 @@ module Check = struct
23512388
in
23522389
Term.Record { label_opt; fields = record_fields }
23532390
in
2391+
let { TypeVariant.term = declared_type } =
2392+
record_variant_for_expr spec expr
2393+
in
23542394
let () =
2355-
check_subsumed_by_opt_labelled_type spec inferred_type label_opt
2356-
~context_expr:expr
2395+
if
2396+
not
2397+
(CheckTypeInstantiations.subsumed spec inferred_type
2398+
declared_type)
2399+
then Error.type_subsumption_failure inferred_type declared_type
23572400
in
23582401
(inferred_type, type_env)
23592402
| RecordUpdate { record_expr; updates } -> (
@@ -2403,8 +2446,19 @@ module Check = struct
24032446
Term.Tuple { label_opt; args = anonymous_typed_args }
24042447
in
24052448
let () =
2406-
check_subsumed_by_opt_labelled_type spec inferred_type label_opt
2407-
~context_expr:expr
2449+
match label_opt with
2450+
| Some label -> (
2451+
match StringMap.find label spec.id_to_defining_node with
2452+
| Node_TypeVariant { TypeVariant.term = declared_type } ->
2453+
if
2454+
not
2455+
(CheckTypeInstantiations.subsumed spec inferred_type
2456+
declared_type)
2457+
then
2458+
Error.type_subsumption_failure inferred_type
2459+
declared_type
2460+
| _ -> Error.invalid_labelled_type label ~context_expr:expr)
2461+
| None -> ()
24082462
in
24092463
(inferred_type, type_env)
24102464
| Relation { is_operator = true; name; args = [ lhs; rhs ] }
@@ -2585,24 +2639,6 @@ module Check = struct
25852639
in
25862640
(List.rev types, type_env)
25872641

2588-
(** [check_subsumed_by_opt_labelled_type spec actual_type label_opt
2589-
~context_expr] checks that [actual_type] is subsumed by the labelled
2590-
type indicated by [label_opt], if any. The [context_expr] is used for
2591-
error reporting. *)
2592-
and check_subsumed_by_opt_labelled_type spec actual_type label_opt
2593-
~context_expr =
2594-
match label_opt with
2595-
| Some label -> (
2596-
match StringMap.find label spec.id_to_defining_node with
2597-
| Node_TypeVariant { TypeVariant.term = declared_type } ->
2598-
if
2599-
not
2600-
(CheckTypeInstantiations.subsumed spec actual_type
2601-
declared_type)
2602-
then Error.type_subsumption_failure actual_type declared_type
2603-
| _ -> Error.invalid_labelled_type label ~context_expr)
2604-
| None -> ()
2605-
26062642
and check_arg_types spec arg_exprs arg_types arg_formal_types ~context_expr
26072643
=
26082644
Utils.list_iter3
@@ -2957,29 +2993,31 @@ module Check = struct
29572993
~expected:(List.length type_components)
29582994
~actual:(List.length args)
29592995
| None -> ())
2960-
| Record { label_opt; fields } -> (
2996+
| Record { fields } ->
29612997
let expr_field_names, expr_field_inits = List.split fields in
29622998
let () = check_expr_list_in_context expr_field_inits in
2963-
match label_opt with
2964-
| Some label ->
2965-
let record_type_fields =
2966-
match StringMap.find label id_to_defining_node with
2967-
| Node_TypeVariant { TypeVariant.term = Record { fields } } ->
2968-
fields
2969-
| _ -> Error.illegal_lhs_application expr
2970-
in
2971-
let record_type_field_names =
2972-
List.map (fun { Term.name } -> name) record_type_fields
2973-
in
2974-
if
2975-
not
2976-
(Utils.list_is_equal String.equal expr_field_names
2977-
record_type_field_names)
2978-
then
2979-
Error.invalid_record_field_names expr expr_field_names
2980-
record_type_field_names
2981-
else ()
2982-
| None -> ())
2999+
let { TypeVariant.term = record_term } =
3000+
record_variant_for_expr spec expr
3001+
in
3002+
let record_type_field_names =
3003+
match record_term with
3004+
| Term.Record { fields = record_fields } ->
3005+
List.map (fun { Term.name } -> name) record_fields
3006+
| _ -> failwith "Expected record term."
3007+
in
3008+
let expr_field_names_sorted =
3009+
List.sort String.compare expr_field_names
3010+
in
3011+
let record_type_field_names_sorted =
3012+
List.sort String.compare record_type_field_names
3013+
in
3014+
if
3015+
not
3016+
(Utils.string_list_is_subset expr_field_names
3017+
record_type_field_names)
3018+
then
3019+
Error.invalid_record_field_names expr expr_field_names_sorted
3020+
record_type_field_names_sorted
29833021
| RecordUpdate { record_expr; updates } ->
29843022
let () = check_expr_in_context record_expr in
29853023
let update_field_names, update_field_inits = List.split updates in
@@ -3421,6 +3459,7 @@ let make_spec_with_builtins ast =
34213459
some_operator = get_relation "some";
34223460
cond_operator = get_relation "cond_op";
34233461
variant_id_to_containing_type = make_variant_id_to_containing_type ast;
3462+
field_to_containing_variant = make_field_to_containing_variant ast;
34243463
}
34253464

34263465
let from_ast ast =

asllib/aslspec/spec.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ val relation_for_id : t -> string -> Relation.t
5656
(** [relation_for_id spec id] returns the relation definition for [id] in
5757
[spec], assuming it is defined as a relation. *)
5858

59+
val record_variant_for_expr : t -> Expr.t -> TypeVariant.t
60+
(** [record_variant_for_expr spec expr] returns the record type variant
61+
corresponding to [expr], assuming [expr] is a record expression. *)
62+
5963
val is_defined_id : t -> string -> bool
6064
(** [is_defined_id spec id] returns [true] if [id] is defined in [spec] and
6165
[false] otherwise. *)

asllib/aslspec/tests.t/rule.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ The relation
6363
{ \condcase[H]{{ \texttt{a} \greaterthan \texttt{b} }}{{ \texttt{a}\numplus\texttt{b} }} }, \\
6464
{ \condcase[H]{{ \texttt{a} \lessthan \texttt{b} }}{\texttt{b}} }
6565
\end{array}} } } } \hva\\
66+
{ { \texttt{y} \eqdef \left\{\begin{array}{lcl}
67+
\FIELDf & : & \texttt{a}\\
68+
\FIELDg & : & \texttt{b}
69+
\end{array}\right\} } } \hva\\
6670
{ { \texttt{r\_f} \eqdef { \texttt{r}.\FIELDf\numplus\texttt{r}.\FIELDg } } } \hva\\
6771
{ { \texttt{r'} \eqdef \texttt{r}\left\{\begin{array}{lcl}
6872
\FIELDf & : & \texttt{res}
@@ -76,6 +80,7 @@ The relation
7680
\AllApply
7781
\begin{itemize}
7882
\item define \texttt{res} as: if \texttt{a} is equal to \texttt{b} holds then \texttt{a}, if \texttt{a} is greater than \texttt{b} holds then the sum of all numbers in \texttt{a} and \texttt{b}, and if \texttt{a} is less than \texttt{b} holds then \texttt{b};
83+
\item define \texttt{y} as: the rec record with $\Fieldf$ value \texttt{a} and $\Fieldg$ value \texttt{b};
7984
\item define \texttt{r\_f} as: the sum of all numbers in the field $\FIELDf$ of \texttt{r} and the field $\FIELDg$ of \texttt{r};
8085
\item define \texttt{r'} as: \texttt{r} with $\FIELDf$ updated to \texttt{res};
8186
\item \textbf{the result is:} the tuple consisting of: (1) \texttt{res} (for \texttt{c}), and (2) \texttt{r'}.

asllib/aslspec/tests.t/rule.spec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
typedef Num;
22

3-
typedef rec = [f: Num, g: Num];
3+
typedef rec = [f: Num, g: Num] { "the rec record with $\Fieldf$ value {f} and $\Fieldg$ value {g}" };
44

55
typing relation r(a: Num, b: Num, r: rec) -> (c: Num, r': rec) {} =
66
res := cond(
77
a = b : a,
88
a > b : a + b,
99
a < b : b
1010
);
11+
y := [f: a, g: b];
1112
r_f := r.f + r.g; // equivalent to r_f := (r.f + r.g);
1213
r' := r(f : res); // This is 'r' with its 'f' field updated to 'res'.
1314
--

asllib/aslspec/utils.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
(** Utility functions for functions that are not available in 4.08. *)
22

3+
module StringSet = Set.Make (String)
4+
35
(***************************************
46
List-related utilities.
57
***************************************)
@@ -60,6 +62,11 @@ let rec list_iter3 f lst1 lst2 lst3 =
6062
list_iter3 f t1 t2 t3
6163
| _ -> invalid_arg "list_iter3: lists have different lengths"
6264

65+
let string_list_is_subset lst1 lst2 =
66+
let set1 = StringSet.of_list lst1 in
67+
let set2 = StringSet.of_list lst2 in
68+
StringSet.subset set1 set2
69+
6370
(***************************************
6471
String-related utilities.
6572
***************************************)

0 commit comments

Comments
 (0)