More agressive re-typing

This commit is contained in:
Denis Merigoux 2023-06-13 20:37:23 +02:00
parent 27bbe78438
commit 2c9b56fb70
No known key found for this signature in database
GPG Key ID: EE99DCFA365C3EE3
2 changed files with 112 additions and 67 deletions

View File

@ -301,9 +301,9 @@ let closure_conversion_scope_let ctx scope_body_expr =
scope_body_expr
let closure_conversion (p : 'm program) : 'm program Bindlib.box =
let (_, new_decl_ctx), new_code_items =
let _, new_code_items =
Scope.fold_map
~f:(fun (toplevel_vars, decl_ctx) var code_item ->
~f:(fun toplevel_vars var code_item ->
match code_item with
| ScopeDef (name, body) ->
let scope_input_var, scope_body_expr =
@ -322,47 +322,8 @@ let closure_conversion (p : 'm program) : 'm program Bindlib.box =
let new_scope_body_expr =
Bindlib.bind_var scope_input_var new_scope_lets
in
let new_decl_ctx =
(* Because closure conversion can change the type of input and
output scope variables that are structs, their type will change.
So we replace their type decleration in the structs with TAny so
that a later re-typing phase can infer them. INVARIANT: the only
types that will change are the types of closures taken in and out
of the scopes. *)
let rec type_contains_arrow t =
match Mark.remove t with
| TArrow _ -> true
| TAny -> true
| TOption t' -> type_contains_arrow t'
| TLit _ -> false
| TArray ts -> type_contains_arrow ts
| TTuple ts -> List.exists type_contains_arrow ts
| TEnum e ->
EnumConstructor.Map.exists
(fun _ t' -> type_contains_arrow t')
(EnumName.Map.find e p.decl_ctx.ctx_enums)
| TStruct s ->
StructField.Map.exists
(fun _ t' -> type_contains_arrow t')
(StructName.Map.find s p.decl_ctx.ctx_structs)
in
let replace_type_with_any s =
Some
(StructField.Map.map
(fun t ->
if type_contains_arrow t then Mark.copy t TAny else t)
(Option.get s))
in
{
decl_ctx with
ctx_structs =
StructName.Map.update body.scope_body_output_struct
replace_type_with_any
(StructName.Map.update body.scope_body_input_struct
replace_type_with_any decl_ctx.ctx_structs);
}
in
( (Var.Set.add var toplevel_vars, new_decl_ctx),
( Var.Set.add var toplevel_vars,
Bindlib.box_apply
(fun scope_body_expr ->
ScopeDef (name, { body with scope_body_expr }))
@ -376,13 +337,56 @@ let closure_conversion (p : 'm program) : 'm program Bindlib.box =
}
in
let _free_vars, new_expr = transform_closures_expr ctx expr in
( (Var.Set.add var toplevel_vars, decl_ctx),
( Var.Set.add var toplevel_vars,
Bindlib.box_apply
(fun e -> Topdef (name, ty, e))
(Expr.Box.lift new_expr) ))
~varf:(fun v -> v)
(Var.Set.empty, p.decl_ctx)
p.code_items
Var.Set.empty p.code_items
in
(* Now we need to further tweak [decl_ctx] because some of the user-defined
types can have closures in them and these closured might have changed type.
So we reset them to [TAny] in the hopes that the transformation applied
will not yield to type unification conflicts. Indeed, consider the
following closure: [let f = if ... then fun v -> x + v else fun v -> v]. To
be typed correctly once converted, this closure needs an existential type
but the Catala typechecker doesn't have them. However, this kind of type
conflict is difficult to produce using the Catala surface language: it can
only happen if you store a closure which is the output of a scope inside a
user-defined data structure, and if you do it in two different places in
the code with two closures that don't have the same capture footprint. *)
let new_decl_ctx =
let rec type_contains_arrow t =
match Mark.remove t with
| TArrow _ -> true
| TAny -> true
| TOption t' -> type_contains_arrow t'
| TLit _ -> false
| TArray ts -> type_contains_arrow ts
| TTuple ts -> List.exists type_contains_arrow ts
| TEnum e ->
EnumConstructor.Map.exists
(fun _ t' -> type_contains_arrow t')
(EnumName.Map.find e p.decl_ctx.ctx_enums)
| TStruct s ->
StructField.Map.exists
(fun _ t' -> type_contains_arrow t')
(StructName.Map.find s p.decl_ctx.ctx_structs)
in
let replace_fun_typs t =
if type_contains_arrow t then Mark.copy t TAny else t
in
{
p.decl_ctx with
ctx_structs =
StructName.Map.map
(StructField.Map.map replace_fun_typs)
p.decl_ctx.ctx_structs;
ctx_enums =
EnumName.Map.map
(EnumConstructor.Map.map replace_fun_typs)
p.decl_ctx.ctx_enums;
}
in
Bindlib.box_apply
(fun new_code_items ->

View File

@ -24,31 +24,72 @@ user-defined types.
```catala-test-inline
$ catala Lcalc --avoid_exceptions -O --closure_conversion
[ERROR] As part of the compilation process, one of the step (closure conversion) modified the Catala program and re-typing after this modification failed with the error message below. This re-typing error if not your fault, but is likely to indicate that the program you are trying to compile is incompatible with the current compilation scheme provided by the Catala compiler. Try to rewrite the program to avoid the problematic pattern or contact the compiler developers for help.
type eoption = | ENone of unit | ESome of any
Error during typechecking, incompatible types:
┌─⯈ (<any> * <any>)
└─⯈ integer → option integer
type Result = { r: eoption (((), integer) → eoption integer * ()); }
Error coming from typechecking the following expression:
┌─⯈ tests/test_scope/bad/scope_call_func_struct_closure.catala_en:10.12-10.13:
└──┐
10 │ internal r content Result
│ ‾
type SubFoo = {
y: eoption
(((eoption integer), integer) → eoption integer * (eoption integer));
}
type Foo = { z: eoption integer; }
Type (<any> * <any>) coming from expression:
┌─⯈ tests/test_scope/bad/scope_call_func_struct_closure.catala_en:10.12-10.13:
└──┐
10 │ internal r content Result
│ ‾
type SubFoo_in = { x_in: eoption integer; }
type Foo_in = { }
let scope SubFoo
(SubFoo_in: SubFoo_in {x_in: eoption integer})
: SubFoo {
y:
eoption
(((eoption integer), integer) → eoption integer *
(eoption integer))
}
=
let get x : eoption integer = SubFoo_in.x_in in
let set y :
eoption
(((eoption integer), integer) → eoption integer * (eoption integer))
=
ESome
(λ (env: (eoption integer)) (z: integer) →
ESome
match
(match env.0 with
| ENone _ → ENone _
| ESome x_0 → ESome (x_0 + z))
with
| ENone _ → raise NoValueProvided
| ESome y → y, (x))
in
return { SubFoo y = y; }
let scope Foo (Foo_in: Foo_in): Foo {z: eoption integer} =
let set r :
eoption Result {r: eoption (((), integer) → eoption integer * ())} =
ESome
{ Result
r =
ESome
(λ (env: any) (param0: integer) →
match (SubFoo { SubFoo_in x_in = ESome 10; }).y with
| ENone _ → ENone _
| ESome r → r.0 r.1 param0, ());
}
in
let set z : eoption integer =
ESome
match
(match (match r with
| ENone _ → ENone _
| ESome r → r.r) with
| ENone _ → ENone _
| ESome r → r.0 r.1 1)
with
| ENone _ → raise NoValueProvided
| ESome z → z
in
return { Foo z = z; }
Type integer → option integer coming from expression:
┌─⯈ tests/test_scope/bad/scope_call_func_struct_closure.catala_en:3.18-3.25:
└─┐
3 │ data r content integer depends on z content integer
│ ‾‾‾‾‾‾‾
#return code 255#
```