diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e583ac56 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +on: + push: + branches-ignore: [ '_**' ] + pull_request: + workflow_dispatch: + +jobs: + nix: + #runs-on: ubuntu-latest + runs-on: [self-hosted, linux, nix] + steps: + #- uses: cachix/install-nix-action@v22 + - uses: actions/checkout@v4 + - run: nix build -L .#charon + - run: nix build -L .#checks.x86_64-linux.tests + - run: nix build -L .#checks.x86_64-linux.tests-polonius + - run: nix build -L .#charon-ml + - run: nix build -L .#checks.x86_64-linux.charon-ml-tests diff --git a/.gitignore b/.gitignore index 08fbd30c..77435dce 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ tests-polonius/ullbc tests-polonius/llbc tests-polonius/target charon-ml/_build +charon-ml/name_matcher_parser/_build ################## # Generated by Nix diff --git a/Makefile b/Makefile index 3352b541..b5078c3f 100644 --- a/Makefile +++ b/Makefile @@ -34,22 +34,29 @@ generate-rust-toolchain-%: cat rust-toolchain.template >> $*/rust-toolchain .PHONY: build -build: build-charon-rust build-charon-ml build-bin-dir +build: build-charon-rust build-charon-ml + +.PHONY: build-debug +build-debug: build-charon-rust-debug build-charon-ml .PHONY: build-charon-rust build-charon-rust: generate-rust-toolchain cd charon && $(MAKE) + mkdir -p bin + cp -f charon/target/release/charon bin + cp -f charon/target/release/charon-driver bin -.PHONY: build-charon-ml -build-charon-ml: - cd charon-ml && $(MAKE) - -.PHONY: build-bin-dir -build-bin-dir: +.PHONY: build-charon-rust-debug +build-charon-rust-debug: generate-rust-toolchain + cd charon && cargo build mkdir -p bin cp -f charon/target/debug/charon bin cp -f charon/target/debug/charon-driver bin +.PHONY: build-charon-ml +build-charon-ml: + cd charon-ml && $(MAKE) + # Build the tests crate, and run the cargo tests .PHONY: build-tests build-tests: diff --git a/README.md b/README.md index a57a3ebe..1a896c28 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ present context, Charon allows us to go from the world of Rust programs to the w formal verification. We are **open to contributions**! Please contact us so that we can coordinate ourselves, -if you are willing to contribute. +if you are willing to contribute. For this purpose you can join the [Zulip](https://aeneas-verif.zulipchat.com/). ## LLBC Charon converts MIR code to ULLBC (Unstructured Low-Level Borrow Calculus) then @@ -95,6 +95,7 @@ Charon executable is located at `bin/charon`. Charon will build the crate and its dependencies, then extract the AST. Charon provides various options and flags to tweak its behaviour: you can display a detailed documentation with `--help`. +In particular, you can print the LLBC generated by Charon with `--print-llbc`. **Remark**: because Charon is compiled with Rust nigthly (this is a requirement to implement a rustc driver), it will build your crate with Rust nightly. You diff --git a/charon-ml/Makefile b/charon-ml/Makefile index a4fa7ad1..35ee3aa4 100644 --- a/charon-ml/Makefile +++ b/charon-ml/Makefile @@ -7,7 +7,7 @@ build: # For instance: `CHARON_LOG=1 make tests`. .PHONY: tests tests: build copy-tests - dune runtest -p charon + dune test # Reformat the code .PHONY: format diff --git a/charon-ml/dune-project b/charon-ml/dune-project index 5a91f49c..12b5c218 100644 --- a/charon-ml/dune-project +++ b/charon-ml/dune-project @@ -1,4 +1,5 @@ (lang dune 3.7) +(using menhir 2.1) (name charon) @@ -29,3 +30,8 @@ Charon. Charon acts as an interface between the rustc compiler and program verification projects. Its purpose is to process Rust .rs files and convert them into files easy to handle by program verifiers.")) + +(package + (name name_matcher_parser) + (synopsis "Parser to define name matchers") + (description "")) \ No newline at end of file diff --git a/charon-ml/name_matcher_parser.opam b/charon-ml/name_matcher_parser.opam new file mode 100644 index 00000000..74c95fa1 --- /dev/null +++ b/charon-ml/name_matcher_parser.opam @@ -0,0 +1,28 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +version: "0.1" +synopsis: "Parser to define name matchers" +description: "" +authors: ["Son Ho" "Jonathan Protzenko" "Aymeric Fromherz" "Sidney Congard"] +license: "Apache-2.0" +homepage: "https://github.com/AeneasVerif/charon" +bug-reports: "https://github.com/AeneasVerif/charon/issues" +depends: [ + "dune" {>= "3.7"} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/AeneasVerif/charon.git" diff --git a/charon-ml/name_matcher_parser/Ast.ml b/charon-ml/name_matcher_parser/Ast.ml new file mode 100644 index 00000000..45bde4ac --- /dev/null +++ b/charon-ml/name_matcher_parser/Ast.ml @@ -0,0 +1,39 @@ +(* TODO: this duplicates PrimitiveValues *) +type big_int = Z.t + +let pp_big_int (fmt : Format.formatter) (bi : big_int) : unit = + Format.pp_print_string fmt (Z.to_string bi) + +let compare_big_int (bi0 : big_int) (bi1 : big_int) : int = Z.compare bi0 bi1 +let show_big_int (bi : big_int) : string = Z.to_string bi + +type var = VarName of string | VarIndex of int [@@deriving show, ord] + +type literal = LInt of big_int | LBool of bool | LChar of char +[@@deriving show, ord] + +(*type const_generic = CgVar of const_generic_var option | CgValue of Z.t*) +type ref_kind = RMut | RShared [@@deriving show, ord] +type region = RVar of var option | RStatic [@@deriving show, ord] +type primitive_adt = TTuple | TArray | TSlice [@@deriving show, ord] + +type pattern = pattern_elem list +and pattern_elem = PIdent of string * generic_args | PImpl of expr + +(** An expression can be a type or a trait ref. + + Note that we put in separate cases the tuple, array, slice and reference + types because they have special syntax. + *) +and expr = + | EComp of pattern + (** Compound expression: instantiated adt, primitive type, constant, etc. + Note that if a type has generic arguments, they will be grouped with + the last pattern elem. + *) + | EPrimAdt of primitive_adt * generic_args + | ERef of region * expr * ref_kind + | EVar of var option + +and generic_arg = GExpr of expr | GValue of literal | GRegion of region +and generic_args = generic_arg list [@@deriving show, ord] diff --git a/charon-ml/name_matcher_parser/Interface.ml b/charon-ml/name_matcher_parser/Interface.ml new file mode 100644 index 00000000..a2e02522 --- /dev/null +++ b/charon-ml/name_matcher_parser/Interface.ml @@ -0,0 +1,15 @@ +open Lexing +include Ast + +let colnum pos = pos.pos_cnum - pos.pos_bol - 1 + +let pos_string pos = + let l = string_of_int pos.pos_lnum and c = string_of_int (colnum pos + 1) in + "line " ^ l ^ ", column " ^ c + +let parse_pattern (s : string) : pattern = + let lexbuf = Lexing.from_string s in + try Parser.full_pattern Lexer.token lexbuf + with Parser.Error -> + raise + (Failure ("Parse error at " ^ pos_string lexbuf.lex_curr_p ^ ":\n" ^ s)) diff --git a/charon-ml/name_matcher_parser/Lexer.mll b/charon-ml/name_matcher_parser/Lexer.mll new file mode 100644 index 00000000..c6676317 --- /dev/null +++ b/charon-ml/name_matcher_parser/Lexer.mll @@ -0,0 +1,41 @@ +{ + open Parser + open Ast +} + +let digit = ['0'-'9'] +let alpha = ['a'-'z' 'A'-'Z'] +let ident = (alpha) (alpha | digit | '_')* +let whitespace = [' ']+ + +(* Rules *) +rule token = parse + | "::" { SEP } + | "mut" { MUT } + | "'static" { STATIC_REGION } + | ''' { REGION (index lexbuf) } + | "true" { TRUE } + | "false" { FALSE } + | ident { IDENT (Lexing.lexeme lexbuf) } + | digit { INT (Z.of_string (Lexing.lexeme lexbuf)) } + | '(' { LEFT_BRACKET } + | ')' { RIGHT_BRACKET } + | '{' { LEFT_CURLY } + | '}' { RIGHT_CURLY } + | '[' { LEFT_SQUARE } + | ']' { RIGHT_SQUARE } + | "@" { VAR(index lexbuf) } + | ';' { SEMICOL } + | '&' { AMPERSAND } + | whitespace { token lexbuf } + | eof { EOF } + | '<' { LEFT_ANGLE } + | '>' { RIGHT_ANGLE } + | ',' { COMMA } + | _ { raise (Failure ("Character not allowed in source text: '" ^ Lexing.lexeme lexbuf ^ "'")) } + +and index = parse + | ident { Some (VarName (Lexing.lexeme lexbuf)) } + | digit+ { Some (VarIndex (int_of_string (Lexing.lexeme lexbuf))) } + | '_' { None } + | "" { None } diff --git a/charon-ml/name_matcher_parser/Parser.mly b/charon-ml/name_matcher_parser/Parser.mly new file mode 100644 index 00000000..df6584c4 --- /dev/null +++ b/charon-ml/name_matcher_parser/Parser.mly @@ -0,0 +1,85 @@ +%{ +open Ast +%} + +%token IDENT +%token VAR // For types and const generics +%token REGION +%token INT +%token STATIC_REGION +%token SEP TRUE FALSE +%token LEFT_BRACKET RIGHT_BRACKET +%token LEFT_CURLY RIGHT_CURLY +%token LEFT_SQUARE RIGHT_SQUARE +%token LEFT_ANGLE RIGHT_ANGLE +%token SEMICOL AMPERSAND MUT COMMA EOF + +/* Types */ + +%type full_pattern +%type pattern +%type pattern_elem +%type expr +%type region +%type generic_args +%type generic_arg + +/* Entry point */ + +%start full_pattern + +%% + +full_pattern: + | n=pattern EOF { n } + +pattern: + | e=pattern_elem { [e] } + | e=pattern_elem SEP n=pattern { e :: n } + +pattern_elem: + // (Instantiated) identifier + | id=IDENT { PIdent (id, []) } + | id=IDENT; LEFT_ANGLE; g=generic_args; RIGHT_ANGLE { PIdent (id, g) } + // Impl path elem + | LEFT_CURLY; ty=expr; RIGHT_CURLY { PImpl ty } + +expr: + // Compound types - note that if a type has generics, they will be grouped + // with the last pattern_elem of the name + | n=pattern { EComp n } + // Primitive ADT: Tuple + | LEFT_BRACKET; tys=separated_list(COMMA, expr); RIGHT_BRACKET { + EPrimAdt (TTuple, List.map (fun x -> GExpr x) tys) } + // Primitive ADT: Slice + | LEFT_SQUARE; ty=expr; RIGHT_SQUARE { + EPrimAdt (TSlice, [GExpr ty]) } + // Primitive ADT: Array + | LEFT_SQUARE; ty=expr; SEMICOL; cg=expr; RIGHT_SQUARE { + EPrimAdt (TArray, [GExpr ty; GExpr cg]) } + // References + | AMPERSAND; r=region; MUT; ty=expr { + ERef (r, ty, RMut) } + | AMPERSAND; r=region; ty=expr { + ERef (r, ty, RShared) } + // Variables + | v=VAR { EVar v } + ; + +region: + | STATIC_REGION { RStatic } + | r=REGION { RVar r } + +generic_args: + | g=generic_arg { [ g ] } + | g=generic_arg; COMMA; gl=generic_args { g :: gl } + +generic_arg: + // Expressions + | e=expr { GExpr e } + // Values + | TRUE { GValue (LBool true) } + | FALSE { GValue (LBool false) } + | v=INT { GValue (LInt v) } + // Regions + | r=region { GRegion r } diff --git a/charon-ml/name_matcher_parser/dune b/charon-ml/name_matcher_parser/dune new file mode 100644 index 00000000..6bf5bd2a --- /dev/null +++ b/charon-ml/name_matcher_parser/dune @@ -0,0 +1,13 @@ +(ocamllex Lexer) +(menhir + (modules Parser) + (flags --explain --inspection --table --dump)) + +(library + (name name_matcher_parser) + (public_name name_matcher_parser) + (preprocess (pps ppx_deriving.show ppx_deriving.ord)) + (libraries zarith menhirLib) + (flags + ; menhir generates instances of the "unused rec flag" warning + (:standard -w -39))) \ No newline at end of file diff --git a/charon-ml/src/Collections.ml b/charon-ml/src/Collections.ml index 6e6f8192..a7616441 100644 --- a/charon-ml/src/Collections.ml +++ b/charon-ml/src/Collections.ml @@ -47,9 +47,15 @@ module List = struct let ls, last = pop_last ls in (x :: ls, last) + (** Return the last element *) + let last (ls : 'a list) : 'a = snd (pop_last ls) + (** Return the n first elements of the list *) let prefix (n : int) (ls : 'a list) : 'a list = fst (split_at ls n) + (** Drop the n first elements of the list *) + let drop (n : int) (ls : 'a list) : 'a list = snd (split_at ls n) + (** Iter and link the iterations. Iterate over a list, but call a function between every two elements @@ -100,6 +106,16 @@ module List = struct let rec repeat (n : int) (x : 'a) : 'a list = if n > 0 then x :: repeat (n - 1) x else [] + + let rec iter_times (n : int) (f : unit -> unit) : unit = + if n > 0 then ( + f (); + iter_times (n - 1) f) + else () + + let filter_mapi (f : int -> 'a -> 'b option) (l : 'a list) : 'b list = + let l = mapi f l in + filter_map (fun x -> x) l end module type OrderedType = sig @@ -137,6 +153,7 @@ module type Map = sig val add_list : (key * 'a) list -> 'a t -> 'a t val of_list : (key * 'a) list -> 'a t + val keys : 'a t -> key list val values : 'a t -> 'a list (** "Simple" pretty printing function. @@ -180,6 +197,7 @@ module MakeMap (Ord : OrderedType) : Map with type key = Ord.t = struct let add_list bl m = List.fold_left (fun s (key, e) -> add key e s) m bl let of_list bl = add_list bl empty + let keys m = List.map fst (bindings m) let values m = List.map snd (bindings m) let to_string indent_opt a_to_string m = @@ -515,3 +533,8 @@ module MakeInjMap (Key : OrderedType) (Elem : OrderedType) : let find_opt k = find_opt k !m in (m, add, mem, find, find_opt) end + +module IntSet = MakeSet (OrderedInt) +module IntMap = MakeMap (OrderedInt) +module StringSet = MakeSet (OrderedString) +module StringMap = MakeMap (OrderedString) diff --git a/charon-ml/src/Expressions.ml b/charon-ml/src/Expressions.ml index 1fc4a975..3752153a 100644 --- a/charon-ml/src/Expressions.ml +++ b/charon-ml/src/Expressions.ml @@ -1,16 +1,31 @@ open Identifiers open Types -open PrimitiveValues +open Values module VarId = IdGen () module GlobalDeclId = Types.GlobalDeclId +module FunDeclId = IdGen () + +type fun_decl_id = FunDeclId.id [@@deriving show, ord] (** We define this type to control the name of the visitor functions (see e.g., {!Charon.UllbcAst.iter_statement_base}). *) type var_id = VarId.id [@@deriving show, ord] +type assumed_fun_id = + | BoxNew + | BoxFree + | ArrayIndexShared + | ArrayIndexMut + | ArrayToSliceShared + | ArrayToSliceMut + | ArrayRepeat + | SliceIndexShared + | SliceIndexMut +[@@deriving show, ord] + (** Ancestor the field_proj_kind iter visitor *) -class ['self] iter_field_proj_kind_base = +class ['self] iter_place_base = object (_self : 'self) inherit [_] VisitorsRuntime.iter method visit_type_decl_id : 'env -> type_decl_id -> unit = fun _ _ -> () @@ -20,7 +35,7 @@ class ['self] iter_field_proj_kind_base = end (** Ancestor the field_proj_kind map visitor *) -class ['self] map_field_proj_kind_base = +class ['self] map_place_base = object (_self : 'self) inherit [_] VisitorsRuntime.map @@ -34,84 +49,16 @@ class ['self] map_field_proj_kind_base = type field_proj_kind = | ProjAdt of type_decl_id * variant_id option - | ProjOption of variant_id - (** Option is an assumed type, coming from the standard library *) | ProjTuple of int (** The integer gives the arity of the tuple *) -[@@deriving - show, - visitors - { - name = "iter_field_proj_kind"; - variety = "iter"; - ancestors = [ "iter_field_proj_kind_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }, - visitors - { - name = "map_field_proj_kind"; - variety = "map"; - ancestors = [ "map_field_proj_kind_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }] (* Remark: no `Index` variant, as it is eliminated by a micro-pass *) -type projection_elem = Deref | DerefBox | Field of field_proj_kind * field_id -[@@deriving - show, - visitors - { - name = "iter_projection_elem"; - variety = "iter"; - ancestors = [ "iter_field_proj_kind" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }, - visitors - { - name = "map_projection_elem"; - variety = "map"; - ancestors = [ "map_field_proj_kind" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }] - -type projection = projection_elem list -[@@deriving - show, - visitors - { - name = "iter_projection"; - variety = "iter"; - ancestors = [ "iter_projection_elem" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }, - visitors - { - name = "map_projection"; - variety = "map"; - ancestors = [ "map_projection_elem" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }] - -(** Ancestor the place iter visitor *) -class ['self] iter_place_base = - object (_self : 'self) - inherit [_] iter_projection - end - -(** Ancestor the place map visitor *) -class ['self] map_place_base = - object (_self : 'self) - inherit [_] map_projection - end +and projection_elem = Deref | DerefBox | Field of field_proj_kind * field_id +and projection = projection_elem list -type place = { var_id : var_id; projection : projection } +and place = { var_id : var_id; projection : projection } [@@deriving show, + ord, visitors { name = "iter_place"; @@ -129,14 +76,7 @@ type place = { var_id : var_id; projection : projection } concrete = true; }] -type borrow_kind = Shared | Mut | TwoPhaseMut | Shallow [@@deriving show] - -(* Remark: no `ArrayToSlice` variant: it gets eliminated in a micro-pass *) -type unop = - | Not - | Neg - | Cast of integer_type * integer_type - (** Cast an integer from a source type to a target type *) +type borrow_kind = BShared | BMut | BTwoPhaseMut | BShallow [@@deriving show, ord] (** A binary operation @@ -185,58 +125,95 @@ let all_binops = Shr; ] -(** Ancestor the operand iter visitor *) -class ['self] iter_operand_base = +(** Ancestor for the constant_expr iter visitor *) +class ['self] iter_constant_expr_base = object (_self : 'self) inherit [_] iter_place - inherit! [_] iter_const_generic - method visit_ety : 'env -> ety -> unit = fun _ _ -> () + inherit! [_] iter_ty + method visit_fun_decl_id : 'env -> fun_decl_id -> unit = fun _ _ -> () + method visit_assumed_fun_id : 'env -> assumed_fun_id -> unit = fun _ _ -> () end -(** Ancestor the operand map visitor *) -class ['self] map_operand_base = +(** Ancestor the constant_expr map visitor *) +class ['self] map_constant_expr_base = object (_self : 'self) inherit [_] map_place - inherit! [_] map_const_generic - method visit_ety : 'env -> ety -> ety = fun _ x -> x + inherit! [_] map_ty + method visit_fun_decl_id : 'env -> fun_decl_id -> fun_decl_id = fun _ x -> x + + method visit_assumed_fun_id : 'env -> assumed_fun_id -> assumed_fun_id = + fun _ x -> x end -type operand = Copy of place | Move of place | Constant of ety * literal +(* TODO: FnPtr *) +type cast_kind = CastInteger of integer_type * integer_type + +(* Remark: no `ArrayToSlice` variant: it gets eliminated in a micro-pass. *) +and unop = + | Not + | Neg + | Cast of cast_kind + (** Cast an integer from a source type to a target type *) + +and raw_constant_expr = + | CLiteral of literal + | CVar of const_generic_var_id + | CTraitConst of trait_ref * generic_args * string + | CFnPtr of fn_ptr + +and constant_expr = { value : raw_constant_expr; ty : ty } + +and fn_ptr = { + func : fun_id_or_trait_method_ref; + generics : generic_args; + trait_and_method_generic_args : generic_args option; +} + +and fun_id_or_trait_method_ref = + | FunId of fun_id + | TraitMethod of trait_ref * string * fun_decl_id + (** The fun decl id is not really needed and here for convenience purposes *) + +(* TODO: prefix with "F" *) +and fun_id = FRegular of fun_decl_id | FAssumed of assumed_fun_id [@@deriving show, + ord, visitors { - name = "iter_operand"; + name = "iter_constant_expr"; variety = "iter"; - ancestors = [ "iter_operand_base" ]; + ancestors = [ "iter_constant_expr_base" ]; nude = true (* Don't inherit {!VisitorsRuntime.iter} *); concrete = true; }, visitors { - name = "map_operand"; + name = "map_constant_expr"; variety = "map"; - ancestors = [ "map_operand_base" ]; + ancestors = [ "map_constant_expr_base" ]; nude = true (* Don't inherit {!VisitorsRuntime.iter} *); concrete = true; }] (** Ancestor the operand iter visitor *) -class ['self] iter_aggregate_kind_base = +class ['self] iter_rvalue_base = object (_self : 'self) - inherit [_] iter_operand - method visit_erased_region : 'env -> erased_region -> unit = fun _ _ -> () + inherit [_] iter_constant_expr + method visit_binop : 'env -> binop -> unit = fun _ _ -> () + method visit_borrow_kind : 'env -> borrow_kind -> unit = fun _ _ -> () end (** Ancestor the operand map visitor *) -class ['self] map_aggregate_kind_base = +class ['self] map_rvalue_base = object (_self : 'self) - inherit [_] map_operand - - method visit_erased_region : 'env -> erased_region -> erased_region = - fun _ x -> x + inherit [_] map_constant_expr + method visit_binop : 'env -> binop -> binop = fun _ x -> x + method visit_borrow_kind : 'env -> borrow_kind -> borrow_kind = fun _ x -> x end +type operand = Copy of place | Move of place | Constant of constant_expr + (** An aggregated ADT. Note that ADTs are desaggregated at some point in MIR. For instance, if @@ -258,59 +235,15 @@ class ['self] map_aggregate_kind_base = [Cons (⊥, ⊥)] upon the first assignment, at which point we can initialize the field 0, etc.). *) -type aggregate_kind = - | AggregatedTuple - | AggregatedOption of variant_id * ety - (* TODO: AggregatedOption should be merged with AggregatedAdt *) - | AggregatedAdt of - type_decl_id - * variant_id option - * erased_region list - * ety list - * const_generic list - | AggregatedRange of ety (* TODO: merge with the Rust *) - | AggregatedArray of ety * const_generic -[@@deriving - show, - visitors - { - name = "iter_aggregate_kind"; - variety = "iter"; - ancestors = [ "iter_aggregate_kind_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }, - visitors - { - name = "map_aggregate_kind"; - variety = "map"; - ancestors = [ "map_aggregate_kind_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }] - -(** Ancestor the rvalue iter visitor *) -class ['self] iter_rvalue_base = - object (_self : 'self) - inherit [_] iter_aggregate_kind - method visit_unop : 'env -> unop -> unit = fun _ _ -> () - method visit_binop : 'env -> binop -> unit = fun _ _ -> () - method visit_borrow_kind : 'env -> borrow_kind -> unit = fun _ _ -> () - end - -(** Ancestor the rvalue map visitor *) -class ['self] map_rvalue_base = - object (_self : 'self) - inherit [_] map_aggregate_kind - method visit_unop : 'env -> unop -> unop = fun _ x -> x - method visit_binop : 'env -> binop -> binop = fun _ x -> x - method visit_borrow_kind : 'env -> borrow_kind -> borrow_kind = fun _ x -> x - end +and aggregate_kind = + | AggregatedAdt of type_id * variant_id option * generic_args + | AggregatedArray of ty * const_generic (* TODO: move the aggregate kind to operands *) -type rvalue = +(* TODO: we should prefix the type variants with "T", this would avoid collisions *) +and rvalue = | Use of operand - | Ref of place * borrow_kind + | RvRef of place * borrow_kind | UnaryOp of unop * operand | BinaryOp of binop * operand * operand | Discriminant of place diff --git a/charon-ml/src/ExpressionsUtils.ml b/charon-ml/src/ExpressionsUtils.ml index 1e63fa12..a30b0e19 100644 --- a/charon-ml/src/ExpressionsUtils.ml +++ b/charon-ml/src/ExpressionsUtils.ml @@ -1,9 +1,9 @@ -module E = Expressions +open Expressions -let unop_can_fail (unop : E.unop) : bool = +let unop_can_fail (unop : unop) : bool = match unop with Neg | Cast _ -> true | Not -> false -let binop_can_fail (binop : E.binop) : bool = +let binop_can_fail (binop : binop) : bool = match binop with | BitXor | BitAnd | BitOr | Eq | Lt | Le | Ne | Ge | Gt -> false | Div | Rem | Add | Sub | Mul | Shl | Shr -> true diff --git a/charon-ml/src/GAst.ml b/charon-ml/src/GAst.ml index 75ff131b..011c903b 100644 --- a/charon-ml/src/GAst.ml +++ b/charon-ml/src/GAst.ml @@ -1,116 +1,58 @@ (** Definitions shared between the ULLBC and the LLBC ASTs. *) - -open Identifiers -open Names open Types -open PrimitiveValues -open Expressions + open Meta -module FunDeclId = IdGen () +open Expressions +module FunDeclId = Expressions.FunDeclId module GlobalDeclId = Expressions.GlobalDeclId +module TraitDeclId = Types.TraitDeclId +module TraitImplId = Types.TraitImplId +module TraitClauseId = Types.TraitClauseId + +type fun_decl_id = FunDeclId.id [@@deriving show, ord] +type assumed_fun_id = Expressions.assumed_fun_id [@@deriving show, ord] +type fun_id = Expressions.fun_id [@@deriving show, ord] + +type fun_id_or_trait_method_ref = Expressions.fun_id_or_trait_method_ref +[@@deriving show, ord] (** A variable, as used in a function definition *) type var = { index : VarId.id; (** Unique variable identifier *) name : string option; - var_ty : ety; + var_ty : ty; (** The variable type - erased type, because variables are not used ** in function signatures: they are only used to declare the list of ** variables manipulated by a function body *) } [@@deriving show] -type assumed_fun_id = - | Replace (** [core::mem::replace] *) - | BoxNew - | BoxDeref (** [core::ops::deref::Deref::>::deref] *) - | BoxDerefMut - (** [core::ops::deref::DerefMut::>::deref_mut] *) - | BoxFree - | VecNew - | VecPush - | VecInsert - | VecLen - | VecIndex (** [core::ops::index::Index::index, usize>] *) - | VecIndexMut - (** [core::ops::index::IndexMut::index_mut, usize>] *) - | ArrayIndexShared - | ArrayIndexMut - | ArrayToSliceShared - | ArrayToSliceMut - | ArraySubsliceShared - | ArraySubsliceMut - | SliceLen - | SliceIndexShared - | SliceIndexMut - | SliceSubsliceShared - | SliceSubsliceMut -[@@deriving show, ord] - -type fun_id = Regular of FunDeclId.id | Assumed of assumed_fun_id -[@@deriving show, ord] - (** Ancestor the AST iter visitors *) class ['self] iter_ast_base = object (_self : 'self) inherit [_] iter_rvalue - inherit! [_] iter_literal - (* Remark: can't inherit iter_literal_type because of a name collision (`Bool`) *) - - method visit_fun_id : 'env -> fun_id -> unit = fun _ _ -> () - method visit_meta : 'env -> meta -> unit = fun _ _ -> () - method visit_integer_type : 'env -> integer_type -> unit = fun _ _ -> () + inherit! [_] iter_predicates end (** Ancestor the AST map visitors *) class ['self] map_ast_base = object (_self : 'self) inherit [_] map_rvalue - inherit! [_] map_literal - (* Remark: can't inherit map_literal_type because of a name collision (`Bool`) *) - - method visit_fun_id : 'env -> fun_id -> fun_id = fun _ x -> x - method visit_meta : 'env -> meta -> meta = fun _ x -> x - - method visit_integer_type : 'env -> integer_type -> integer_type = - fun _ x -> x + inherit! [_] map_predicates end +(* Below: the types need not be mutually recursive, but it makes it easier + to derive the visitors *) type assertion = { cond : operand; expected : bool } -[@@deriving - show, - visitors - { - name = "iter_assertion"; - variety = "iter"; - ancestors = [ "iter_ast_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }, - visitors - { - name = "map_assertion"; - variety = "map"; - ancestors = [ "map_ast_base" ]; - nude = true (* Don't inherit {!VisitorsRuntime.iter} *); - concrete = true; - }] -type call = { - func : fun_id; - region_args : erased_region list; - type_args : ety list; - const_generic_args : const_generic list; - args : operand list; - dest : place; -} +and call = { func : fn_ptr; args : operand list; dest : place } [@@deriving show, visitors { name = "iter_call"; variety = "iter"; - ancestors = [ "iter_assertion" ]; + ancestors = [ "iter_ast_base" ]; nude = true (* Don't inherit {!VisitorsRuntime.iter} *); concrete = true; }, @@ -118,7 +60,7 @@ type call = { { name = "map_call"; variety = "map"; - ancestors = [ "map_assertion" ]; + ancestors = [ "map_ast_base" ]; nude = true (* Don't inherit {!VisitorsRuntime.iter} *); concrete = true; }] @@ -135,26 +77,45 @@ class ['self] map_statement_base = inherit [_] map_call end -(** A function signature, as used when declaring functions *) -type fun_sig = { - region_params : region_var list; - num_early_bound_regions : int; - regions_hierarchy : region_var_groups; - type_params : type_var list; - (** The type parameters can be indexed with {!Types.TypeVarId.id}. - - See {!Identifiers.Id.mapi} for instance. - *) - const_generic_params : const_generic_var list; - (** The const generic parameters can be indexed with {!Types.ConstGenericVarId.id}. +type params_info = { + num_region_params : int; + num_type_params : int; + num_const_generic_params : int; + num_trait_clauses : int; + num_regions_outlive : int; + num_types_outlive : int; + num_trait_type_constraints : int; +} +[@@deriving show] - See {!Identifiers.Id.mapi} for instance. - *) - inputs : sty list; - output : sty; +(** A function signature for function declarations *) +type fun_sig = { + is_unsafe : bool; + generics : generic_params; + preds : predicates; + parent_params_info : params_info option; + inputs : ty list; + output : ty; } [@@deriving show] +type fun_kind = + | RegularKind (** A "normal" function *) + | TraitMethodImpl of trait_impl_id * trait_decl_id * string * bool + (** Trait method implementation. + + Fields: + - [trait impl id] + - [trait_id] + - [method_name] + - [provided]: true if this function re-implements a provided method + *) + | TraitMethodDecl of trait_decl_id * string (** A trait method declaration *) + | TraitMethodProvided of trait_decl_id * string + (** Trait method provided function (trait method declaration which defines a + default implementation at the same time *) +[@@deriving show] + type 'body gexpr_body = { meta : meta; arg_count : int; @@ -170,14 +131,47 @@ type 'body gexpr_body = { type 'body gfun_decl = { def_id : FunDeclId.id; meta : meta; - name : fun_name; + is_local : bool; + name : name; signature : fun_sig; + kind : fun_kind; body : 'body gexpr_body option; is_global_decl_body : bool; } [@@deriving show] -type 'id g_declaration_group = NonRec of 'id | Rec of 'id list +type trait_decl = { + def_id : trait_decl_id; + is_local : bool; + name : name; + meta : meta; + generics : generic_params; + preds : predicates; + parent_clauses : trait_clause list; + consts : (trait_item_name * (ty * global_decl_id option)) list; + types : (trait_item_name * (trait_clause list * ty option)) list; + required_methods : (trait_item_name * fun_decl_id) list; + provided_methods : (trait_item_name * fun_decl_id option) list; +} +[@@deriving show] + +type trait_impl = { + def_id : trait_impl_id; + is_local : bool; + name : name; + meta : meta; + impl_trait : trait_decl_ref; + generics : generic_params; + preds : predicates; + parent_trait_refs : trait_ref list; + consts : (trait_item_name * (ty * global_decl_id)) list; + types : (trait_item_name * (trait_ref list * ty)) list; + required_methods : (trait_item_name * fun_decl_id) list; + provided_methods : (trait_item_name * fun_decl_id) list; +} +[@@deriving show] + +type 'id g_declaration_group = NonRecGroup of 'id | RecGroup of 'id list [@@deriving show] type type_declaration_group = TypeDeclId.id g_declaration_group @@ -187,17 +181,31 @@ type fun_declaration_group = FunDeclId.id g_declaration_group [@@deriving show] (** Module declaration. Globals cannot be mutually recursive. *) type declaration_group = - | Type of type_declaration_group - | Fun of fun_declaration_group - | Global of GlobalDeclId.id + | TypeGroup of type_declaration_group + | FunGroup of fun_declaration_group + | GlobalGroup of GlobalDeclId.id + | TraitDeclGroup of TraitDeclId.id + | TraitImplGroup of TraitImplId.id +[@@deriving show] + +type 'body gglobal_decl = { + meta : meta; + def_id : GlobalDeclId.id; + is_local : bool; + name : name; + ty : ty; + body : 'body; +} [@@deriving show] (** A crate *) -type ('fun_decl, 'global_decl) gcrate = { +type ('fun_body, 'global_body) gcrate = { name : string; declarations : declaration_group list; - types : type_decl TypeDeclId.Map.t; - functions : 'fun_decl FunDeclId.Map.t; - globals : 'global_decl GlobalDeclId.Map.t; + type_decls : type_decl TypeDeclId.Map.t; + fun_decls : 'fun_body gfun_decl FunDeclId.Map.t; + global_decls : 'global_body gglobal_decl GlobalDeclId.Map.t; + trait_decls : trait_decl TraitDeclId.Map.t; + trait_impls : trait_impl TraitImplId.Map.t; } [@@deriving show] diff --git a/charon-ml/src/GAstOfJson.ml b/charon-ml/src/GAstOfJson.ml index 329f4df5..0c116ccb 100644 --- a/charon-ml/src/GAstOfJson.ml +++ b/charon-ml/src/GAstOfJson.ml @@ -9,17 +9,14 @@ *) open Yojson.Basic -open Names open OfJsonBasic open Identifiers open Meta -module T = Types -module PV = PrimitiveValues -module S = Scalars -module E = Expressions -module A = GAst -module TU = TypesUtils -module AU = LlbcAstUtils +open Values +open Types +open Scalars +open Expressions +open GAst module LocalFileId = IdGen () module VirtualFileId = IdGen () @@ -120,602 +117,824 @@ let meta_of_json (id_to_file : id_to_file_map) (js : json) : Ok { span; generated_from_span } | _ -> Error "") -let path_elem_of_json (js : json) : (path_elem, string) result = - combine_error_msgs js __FUNCTION__ - (match js with - | `Assoc [ ("Ident", name) ] -> - let* name = string_of_json name in - Ok (Ident name) - | `Assoc [ ("Disambiguator", d) ] -> - let* d = Disambiguator.id_of_json d in - Ok (Disambiguator d) - | _ -> Error "") - -let name_of_json (js : json) : (name, string) result = - combine_error_msgs js __FUNCTION__ (list_of_json path_elem_of_json js) - -let fun_name_of_json (js : json) : (fun_name, string) result = - combine_error_msgs js __FUNCTION__ (name_of_json js) - -let type_var_of_json (js : json) : (T.type_var, string) result = +let type_var_of_json (js : json) : (type_var, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("index", index); ("name", name) ] -> - let* index = T.TypeVarId.id_of_json index in + let* index = TypeVarId.id_of_json index in let* name = string_of_json name in - Ok { T.index; name } + Ok { index; name } | _ -> Error "") -let region_var_of_json (js : json) : (T.region_var, string) result = +let region_var_of_json (js : json) : (region_var, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("index", index); ("name", name) ] -> - let* index = T.RegionVarId.id_of_json index in + let* index = RegionId.id_of_json index in let* name = string_option_of_json name in - Ok { T.index; name } + Ok { index; name } | _ -> Error "") -let region_of_json (js : json) : (T.RegionVarId.id T.region, string) result = +let region_of_json (js : json) : (region, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `String "Static" -> Ok T.Static + | `String "Static" -> Ok RStatic + | `String "Erased" -> Ok RErased | `Assoc [ ("Var", rid) ] -> - let* rid = T.RegionVarId.id_of_json rid in - Ok (T.Var rid : T.RegionVarId.id T.region) + let* rid = RegionId.id_of_json rid in + Ok (RVar rid : region) | _ -> Error "") -let erased_region_of_json (js : json) : (T.erased_region, string) result = - combine_error_msgs js __FUNCTION__ - (match js with `String "Erased" -> Ok T.Erased | _ -> Error "") - -let integer_type_of_json (js : json) : (PV.integer_type, string) result = +let integer_type_of_json (js : json) : (integer_type, string) result = match js with - | `String "Isize" -> Ok PV.Isize - | `String "I8" -> Ok PV.I8 - | `String "I16" -> Ok PV.I16 - | `String "I32" -> Ok PV.I32 - | `String "I64" -> Ok PV.I64 - | `String "I128" -> Ok PV.I128 - | `String "Usize" -> Ok PV.Usize - | `String "U8" -> Ok PV.U8 - | `String "U16" -> Ok PV.U16 - | `String "U32" -> Ok PV.U32 - | `String "U64" -> Ok PV.U64 - | `String "U128" -> Ok PV.U128 + | `String "Isize" -> Ok Isize + | `String "I8" -> Ok I8 + | `String "I16" -> Ok I16 + | `String "I32" -> Ok I32 + | `String "I64" -> Ok I64 + | `String "I128" -> Ok I128 + | `String "Usize" -> Ok Usize + | `String "U8" -> Ok U8 + | `String "U16" -> Ok U16 + | `String "U32" -> Ok U32 + | `String "U64" -> Ok U64 + | `String "U128" -> Ok U128 | _ -> Error ("integer_type_of_json failed on: " ^ show js) -let literal_type_of_json (js : json) : (PV.literal_type, string) result = +let literal_type_of_json (js : json) : (literal_type, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Integer", int_ty) ] -> let* int_ty = integer_type_of_json int_ty in - Ok (PV.Integer int_ty) - | `String "Bool" -> Ok PV.Bool - | `String "Char" -> Ok PV.Char + Ok (TInteger int_ty) + | `String "Bool" -> Ok TBool + | `String "Char" -> Ok TChar | _ -> Error "") -let const_generic_var_of_json (js : json) : (T.const_generic_var, string) result - = +let const_generic_var_of_json (js : json) : (const_generic_var, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("index", index); ("name", name); ("ty", ty) ] -> - let* index = T.ConstGenericVarId.id_of_json index in + let* index = ConstGenericVarId.id_of_json index in let* name = string_of_json name in let* ty = literal_type_of_json ty in - Ok { T.index; name; ty } + Ok { index; name; ty } | _ -> Error "") -let ref_kind_of_json (js : json) : (T.ref_kind, string) result = +let ref_kind_of_json (js : json) : (ref_kind, string) result = match js with - | `String "Mut" -> Ok T.Mut - | `String "Shared" -> Ok T.Shared + | `String "Mut" -> Ok RMut + | `String "Shared" -> Ok RShared | _ -> Error ("ref_kind_of_json failed on: " ^ show js) -let assumed_ty_of_json (js : json) : (T.assumed_ty, string) result = +let assumed_ty_of_json (js : json) : (assumed_ty, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `String "Box" -> Ok T.Box - | `String "Vec" -> Ok T.Vec - | `String "Option" -> Ok T.Option - | `String "Array" -> Ok T.Array - | `String "Slice" -> Ok T.Slice - | `String "Str" -> Ok T.Str - | `String "Range" -> Ok T.Range + | `String "Box" -> Ok TBox + | `String "Array" -> Ok TArray + | `String "Slice" -> Ok TSlice + | `String "Str" -> Ok TStr | _ -> Error "") -let type_id_of_json (js : json) : (T.type_id, string) result = +let type_id_of_json (js : json) : (type_id, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Adt", id) ] -> - let* id = T.TypeDeclId.id_of_json id in - Ok (T.AdtId id) - | `String "Tuple" -> Ok T.Tuple + let* id = TypeDeclId.id_of_json id in + Ok (TAdtId id) + | `String "Tuple" -> Ok TTuple | `Assoc [ ("Assumed", aty) ] -> let* aty = assumed_ty_of_json aty in - Ok (T.Assumed aty) + Ok (TAssumed aty) | _ -> Error "") -let big_int_of_json (js : json) : (PV.big_int, string) result = +let big_int_of_json (js : json) : (big_int, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Int i -> Ok (Z.of_int i) | `String is -> Ok (Z.of_string is) | _ -> Error "") -(** Deserialize a {!PV.scalar_value} from JSON and **check the ranges**. +(** Deserialize a {!Values.scalar_value} from JSON and **check the ranges**. Note that in practice we also check that the values are in range in the interpreter functions. Still, it doesn't cost much to be a bit conservative. *) -let scalar_value_of_json (js : json) : (PV.scalar_value, string) result = +let scalar_value_of_json (js : json) : (scalar_value, string) result = let res = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Isize", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = Isize } + Ok { value = bi; int_ty = Isize } | `Assoc [ ("I8", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = I8 } + Ok { value = bi; int_ty = I8 } | `Assoc [ ("I16", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = I16 } + Ok { value = bi; int_ty = I16 } | `Assoc [ ("I32", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = I32 } + Ok { value = bi; int_ty = I32 } | `Assoc [ ("I64", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = I64 } + Ok { value = bi; int_ty = I64 } | `Assoc [ ("I128", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = I128 } + Ok { value = bi; int_ty = I128 } | `Assoc [ ("Usize", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = Usize } + Ok { value = bi; int_ty = Usize } | `Assoc [ ("U8", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = U8 } + Ok { value = bi; int_ty = U8 } | `Assoc [ ("U16", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = U16 } + Ok { value = bi; int_ty = U16 } | `Assoc [ ("U32", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = U32 } + Ok { value = bi; int_ty = U32 } | `Assoc [ ("U64", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = U64 } + Ok { value = bi; int_ty = U64 } | `Assoc [ ("U128", bi) ] -> let* bi = big_int_of_json bi in - Ok { PV.value = bi; int_ty = U128 } + Ok { value = bi; int_ty = U128 } | _ -> Error "") in match res with | Error _ -> res | Ok sv -> - if not (S.check_scalar_value_in_range sv) then ( - log#serror ("Scalar value not in range: " ^ PV.show_scalar_value sv); - raise - (Failure ("Scalar value not in range: " ^ PV.show_scalar_value sv))); + if not (check_scalar_value_in_range sv) then ( + log#serror ("Scalar value not in range: " ^ show_scalar_value sv); + raise (Failure ("Scalar value not in range: " ^ show_scalar_value sv))); res -let literal_of_json (js : json) : (PV.literal, string) result = +let literal_of_json (js : json) : (literal, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Scalar", v) ] -> let* v = scalar_value_of_json v in - Ok (PV.Scalar v) + Ok (VScalar v) | `Assoc [ ("Bool", v) ] -> let* v = bool_of_json v in - Ok (PV.Bool v) + Ok (VBool v) | `Assoc [ ("Char", v) ] -> let* v = char_of_json v in - Ok (PV.Char v) + Ok (VChar v) | _ -> Error "") -let const_generic_of_json (js : json) : (T.const_generic, string) result = +let const_generic_of_json (js : json) : (const_generic, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Global", id) ] -> - let* id = E.GlobalDeclId.id_of_json id in - Ok (T.ConstGenericGlobal id) + let* id = GlobalDeclId.id_of_json id in + Ok (CgGlobal id) | `Assoc [ ("Var", id) ] -> - let* id = T.ConstGenericVarId.id_of_json id in - Ok (T.ConstGenericVar id) + let* id = ConstGenericVarId.id_of_json id in + Ok (CgVar id) | `Assoc [ ("Value", lit) ] -> let* lit = literal_of_json lit in - Ok (T.ConstGenericValue lit) + Ok (CgValue lit) | _ -> Error "") -let rec ty_of_json (r_of_json : json -> ('r, string) result) (js : json) : - ('r T.ty, string) result = +let rec ty_of_json (js : json) : (ty, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `Assoc [ ("Adt", `List [ id; regions; types; cgs ]) ] -> + | `Assoc [ ("Adt", `List [ id; generics ]) ] -> let* id = type_id_of_json id in - let* regions = list_of_json r_of_json regions in - let* types = list_of_json (ty_of_json r_of_json) types in - let* cgs = list_of_json const_generic_of_json cgs in + let* generics = generic_args_of_json generics in (* Sanity check *) - (match id with T.Tuple -> assert (List.length regions = 0) | _ -> ()); - Ok (T.Adt (id, regions, types, cgs)) + (match id with + | TTuple -> assert (generics.regions = [] && generics.trait_refs = []) + | _ -> ()); + Ok (TAdt (id, generics)) | `Assoc [ ("TypeVar", id) ] -> - let* id = T.TypeVarId.id_of_json id in - Ok (T.TypeVar id) + let* id = TypeVarId.id_of_json id in + Ok (TVar id) | `Assoc [ ("Literal", ty) ] -> let* ty = literal_type_of_json ty in - Ok (T.Literal ty) + Ok (TLiteral ty) | `Assoc [ ("Ref", `List [ region; ty; ref_kind ]) ] -> - let* region = r_of_json region in - let* ty = ty_of_json r_of_json ty in + let* region = region_of_json region in + let* ty = ty_of_json ty in let* ref_kind = ref_kind_of_json ref_kind in - Ok (T.Ref (region, ty, ref_kind)) + Ok (TRef (region, ty, ref_kind)) + | `Assoc [ ("RawPtr", `List [ ty; ref_kind ]) ] -> + let* ty = ty_of_json ty in + let* ref_kind = ref_kind_of_json ref_kind in + Ok (TRawPtr (ty, ref_kind)) + | `Assoc [ ("TraitType", `List [ trait_ref; generics; item_name ]) ] -> + let* trait_ref = trait_ref_of_json trait_ref in + let* generics = generic_args_of_json generics in + let* item_name = string_of_json item_name in + Ok (TTraitType (trait_ref, generics, item_name)) + | `Assoc [ ("Arrow", `List [ inputs; output ]) ] -> + let* inputs = list_of_json ty_of_json inputs in + let* output = ty_of_json output in + Ok (TArrow (inputs, output)) + | _ -> Error "") + +and trait_ref_of_json (js : json) : (trait_ref, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("trait_id", trait_id); + ("generics", generics); + ("trait_decl_ref", trait_decl_ref); + ] -> + let* trait_id = trait_instance_id_of_json trait_id in + let* generics = generic_args_of_json generics in + let* trait_decl_ref = trait_decl_ref_of_json trait_decl_ref in + Ok ({ trait_id; generics; trait_decl_ref } : trait_ref) + | _ -> Error "") + +and trait_decl_ref_of_json (js : json) : (trait_decl_ref, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("trait_id", trait_id); ("generics", generics) ] -> + let* trait_decl_id = TraitDeclId.id_of_json trait_id in + let* decl_generics = generic_args_of_json generics in + Ok ({ trait_decl_id; decl_generics } : trait_decl_ref) | _ -> Error "") -let sty_of_json (js : json) : (T.sty, string) result = - combine_error_msgs js __FUNCTION__ (ty_of_json region_of_json js) +and generic_args_of_json (js : json) : (generic_args, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("regions", regions); + ("types", types); + ("const_generics", const_generics); + ("trait_refs", trait_refs); + ] -> + let* regions = list_of_json region_of_json regions in + let* types = list_of_json ty_of_json types in + let* const_generics = + list_of_json const_generic_of_json const_generics + in + let* trait_refs = list_of_json trait_ref_of_json trait_refs in + Ok { regions; types; const_generics; trait_refs } + | _ -> Error "") -let ety_of_json (js : json) : (T.ety, string) result = - combine_error_msgs js __FUNCTION__ (ty_of_json erased_region_of_json js) +and trait_instance_id_of_json (js : json) : (trait_instance_id, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `String "SelfId" -> Ok Self + | `Assoc [ ("TraitImpl", id) ] -> + let* id = TraitImplId.id_of_json id in + Ok (TraitImpl id) + | `Assoc [ ("BuiltinOrAuto", id) ] -> + let* id = TraitDeclId.id_of_json id in + Ok (BuiltinOrAuto id) + | `Assoc [ ("Clause", id) ] -> + let* id = TraitClauseId.id_of_json id in + Ok (Clause id) + | `Assoc [ ("ParentClause", `List [ inst_id; decl_id; clause_id ]) ] -> + let* inst_id = trait_instance_id_of_json inst_id in + let* decl_id = TraitDeclId.id_of_json decl_id in + let* clause_id = TraitClauseId.id_of_json clause_id in + Ok (ParentClause (inst_id, decl_id, clause_id)) + | `Assoc + [ ("ItemClause", `List [ inst_id; decl_id; item_name; clause_id ]) ] -> + let* inst_id = trait_instance_id_of_json inst_id in + let* decl_id = TraitDeclId.id_of_json decl_id in + let* item_name = string_of_json item_name in + let* clause_id = TraitClauseId.id_of_json clause_id in + Ok (ItemClause (inst_id, decl_id, item_name, clause_id)) + | `Assoc [ ("FnPointer", ty) ] -> + let* ty = ty_of_json ty in + Ok (FnPointer ty) + | _ -> Error "") let field_of_json (id_to_file : id_to_file_map) (js : json) : - (T.field, string) result = + (field, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("meta", meta); ("name", name); ("ty", ty) ] -> let* meta = meta_of_json id_to_file meta in let* name = option_of_json string_of_json name in - let* ty = sty_of_json ty in - Ok { T.meta; field_name = name; field_ty = ty } + let* ty = ty_of_json ty in + Ok { meta; field_name = name; field_ty = ty } | _ -> Error "") let variant_of_json (id_to_file : id_to_file_map) (js : json) : - (T.variant, string) result = + (variant, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("meta", meta); ("name", name); ("fields", fields) ] -> let* meta = meta_of_json id_to_file meta in let* name = string_of_json name in let* fields = list_of_json (field_of_json id_to_file) fields in - Ok { T.meta; variant_name = name; fields } + Ok { meta; variant_name = name; fields } | _ -> Error "") let type_decl_kind_of_json (id_to_file : id_to_file_map) (js : json) : - (T.type_decl_kind, string) result = + (type_decl_kind, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Struct", fields) ] -> let* fields = list_of_json (field_of_json id_to_file) fields in - Ok (T.Struct fields) + Ok (Struct fields) | `Assoc [ ("Enum", variants) ] -> let* variants = list_of_json (variant_of_json id_to_file) variants in - Ok (T.Enum variants) - | `String "Opaque" -> Ok T.Opaque + Ok (Enum variants) + | `String "Opaque" -> Ok Opaque | _ -> Error "") -let region_var_group_of_json (js : json) : (T.region_var_group, string) result = +let region_var_group_of_json (js : json) : (region_group, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("id", id); ("regions", regions); ("parents", parents) ] -> - let* id = T.RegionGroupId.id_of_json id in - let* regions = list_of_json T.RegionVarId.id_of_json regions in - let* parents = list_of_json T.RegionGroupId.id_of_json parents in - Ok { T.id; regions; parents } + let* id = RegionGroupId.id_of_json id in + let* regions = list_of_json RegionId.id_of_json regions in + let* parents = list_of_json RegionGroupId.id_of_json parents in + Ok { id; regions; parents } | _ -> Error "") -let region_var_groups_of_json (js : json) : (T.region_var_groups, string) result - = +let region_var_groups_of_json (js : json) : (region_groups, string) result = combine_error_msgs js __FUNCTION__ (list_of_json region_var_group_of_json js) +let trait_clause_of_json (id_to_file : id_to_file_map) (js : json) : + (trait_clause, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("clause_id", clause_id); + ("meta", meta); + ("trait_id", trait_id); + ("generics", generics); + ] -> + let* clause_id = TraitClauseId.id_of_json clause_id in + let* meta = option_of_json (meta_of_json id_to_file) meta in + let* trait_id = TraitDeclId.id_of_json trait_id in + let* clause_generics = generic_args_of_json generics in + Ok ({ clause_id; meta; trait_id; clause_generics } : trait_clause) + | _ -> Error "") + +let generic_params_of_json (id_to_file : id_to_file_map) (js : json) : + (generic_params, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("regions", regions); + ("types", types); + ("const_generics", const_generics); + ("trait_clauses", trait_clauses); + ] -> + let* regions = list_of_json region_var_of_json regions in + let* types = list_of_json type_var_of_json types in + let* const_generics = + list_of_json const_generic_var_of_json const_generics + in + let* trait_clauses = + list_of_json (trait_clause_of_json id_to_file) trait_clauses + in + Ok { regions; types; const_generics; trait_clauses } + | _ -> Error "") + +let region_outlives_of_json (js : json) : (region_outlives, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `List [ r0; r1 ] -> + let* r0 = region_of_json r0 in + let* r1 = region_of_json r1 in + Ok (r0, r1) + | _ -> Error "") + +let type_outlives_of_json (js : json) : (type_outlives, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `List [ ty; r ] -> + let* ty = ty_of_json ty in + let* r = region_of_json r in + Ok (ty, r) + | _ -> Error "") + +let trait_type_constraint_of_json (js : json) : + (trait_type_constraint, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("trait_ref", trait_ref); + ("generics", generics); + ("type_name", type_name); + ("ty", ty); + ] -> + let* trait_ref = trait_ref_of_json trait_ref in + let* generics = generic_args_of_json generics in + let* type_name = string_of_json type_name in + let* ty = ty_of_json ty in + Ok ({ trait_ref; generics; type_name; ty } : trait_type_constraint) + | _ -> Error "") + +let predicates_of_json (js : json) : (predicates, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("regions_outlive", regions_outlive); + ("types_outlive", types_outlive); + ("trait_type_constraints", trait_type_constraints); + ] -> + let* regions_outlive = + list_of_json region_outlives_of_json regions_outlive + in + let* types_outlive = list_of_json type_outlives_of_json types_outlive in + let* trait_type_constraints = + list_of_json trait_type_constraint_of_json trait_type_constraints + in + Ok { regions_outlive; types_outlive; trait_type_constraints } + | _ -> Error "") + +let impl_elem_of_json (id_to_file : id_to_file_map) (js : json) : + (impl_elem, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("generics", generics); + ("preds", preds); + ("ty", ty); + ("disambiguator", disambiguator); + ] -> + let* generics = generic_params_of_json id_to_file generics in + let* preds = predicates_of_json preds in + let* ty = ty_of_json ty in + let* disambiguator = Disambiguator.id_of_json disambiguator in + Ok { generics; preds; ty; disambiguator } + | _ -> Error "") + +let path_elem_of_json (id_to_file : id_to_file_map) (js : json) : + (path_elem, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("Ident", `List [ name; d ]) ] -> + let* name = string_of_json name in + let* d = Disambiguator.id_of_json d in + Ok (PeIdent (name, d)) + | `Assoc [ ("Impl", impl) ] -> + let* d = impl_elem_of_json id_to_file impl in + Ok (PeImpl d) + | _ -> Error "") + +let name_of_json (id_to_file : id_to_file_map) (js : json) : + (name, string) result = + combine_error_msgs js __FUNCTION__ + (list_of_json (path_elem_of_json id_to_file) js) + let type_decl_of_json (id_to_file : id_to_file_map) (js : json) : - (T.type_decl, string) result = + (type_decl, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("def_id", def_id); ("meta", meta); + ("is_local", is_local); ("name", name); - ("region_params", region_params); - ("type_params", type_params); - ("const_generic_params", const_generic_params); + ("generics", generics); + ("preds", preds); ("kind", kind); - ("regions_hierarchy", regions_hierarchy); ] -> - let* def_id = T.TypeDeclId.id_of_json def_id in + let* def_id = TypeDeclId.id_of_json def_id in let* meta = meta_of_json id_to_file meta in - let* name = name_of_json name in - let* region_params = list_of_json region_var_of_json region_params in - let* type_params = list_of_json type_var_of_json type_params in - let* const_generic_params = - list_of_json const_generic_var_of_json const_generic_params - in + let* is_local = bool_of_json is_local in + let* name = name_of_json id_to_file name in + let* generics = generic_params_of_json id_to_file generics in + let* preds = predicates_of_json preds in let* kind = type_decl_kind_of_json id_to_file kind in - let* regions_hierarchy = region_var_groups_of_json regions_hierarchy in - Ok - { - T.def_id; - meta; - name; - region_params; - type_params; - const_generic_params; - kind; - regions_hierarchy; - } + Ok { def_id; meta; is_local; name; generics; preds; kind } | _ -> Error "") -let var_of_json (js : json) : (A.var, string) result = +let var_of_json (js : json) : (var, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("index", index); ("name", name); ("ty", ty) ] -> - let* index = E.VarId.id_of_json index in + let* index = VarId.id_of_json index in let* name = string_option_of_json name in - let* var_ty = ety_of_json ty in - Ok { A.index; name; var_ty } + let* var_ty = ty_of_json ty in + Ok { index; name; var_ty } | _ -> Error "") -let field_proj_kind_of_json (js : json) : (E.field_proj_kind, string) result = +let field_proj_kind_of_json (js : json) : (field_proj_kind, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("ProjAdt", `List [ def_id; opt_variant_id ]) ] -> - let* def_id = T.TypeDeclId.id_of_json def_id in + let* def_id = TypeDeclId.id_of_json def_id in let* opt_variant_id = - option_of_json T.VariantId.id_of_json opt_variant_id + option_of_json VariantId.id_of_json opt_variant_id in - Ok (E.ProjAdt (def_id, opt_variant_id)) + Ok (ProjAdt (def_id, opt_variant_id)) | `Assoc [ ("ProjTuple", i) ] -> let* i = int_of_json i in - Ok (E.ProjTuple i) - | `Assoc [ ("ProjOption", variant_id) ] -> - let* variant_id = T.VariantId.id_of_json variant_id in - Ok (E.ProjOption variant_id) + Ok (ProjTuple i) | _ -> Error "") -let projection_elem_of_json (js : json) : (E.projection_elem, string) result = +let projection_elem_of_json (js : json) : (projection_elem, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `String "Deref" -> Ok E.Deref - | `String "DerefBox" -> Ok E.DerefBox + | `String "Deref" -> Ok Deref + | `String "DerefBox" -> Ok DerefBox | `Assoc [ ("Field", `List [ proj_kind; field_id ]) ] -> let* proj_kind = field_proj_kind_of_json proj_kind in - let* field_id = T.FieldId.id_of_json field_id in - Ok (E.Field (proj_kind, field_id)) + let* field_id = FieldId.id_of_json field_id in + Ok (Field (proj_kind, field_id)) | _ -> Error ("projection_elem_of_json failed on:" ^ show js)) -let projection_of_json (js : json) : (E.projection, string) result = +let projection_of_json (js : json) : (projection, string) result = combine_error_msgs js __FUNCTION__ (list_of_json projection_elem_of_json js) -let place_of_json (js : json) : (E.place, string) result = +let place_of_json (js : json) : (place, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("var_id", var_id); ("projection", projection) ] -> - let* var_id = E.VarId.id_of_json var_id in + let* var_id = VarId.id_of_json var_id in let* projection = projection_of_json projection in - Ok { E.var_id; projection } + Ok { var_id; projection } | _ -> Error "") -let borrow_kind_of_json (js : json) : (E.borrow_kind, string) result = +let borrow_kind_of_json (js : json) : (borrow_kind, string) result = match js with - | `String "Shared" -> Ok E.Shared - | `String "Mut" -> Ok E.Mut - | `String "TwoPhaseMut" -> Ok E.TwoPhaseMut - | `String "Shallow" -> Ok E.Shallow + | `String "Shared" -> Ok BShared + | `String "Mut" -> Ok BMut + | `String "TwoPhaseMut" -> Ok BTwoPhaseMut + | `String "Shallow" -> Ok BShallow | _ -> Error ("borrow_kind_of_json failed on:" ^ show js) -let unop_of_json (js : json) : (E.unop, string) result = - match js with - | `String "Not" -> Ok E.Not - | `String "Neg" -> Ok E.Neg - | `Assoc [ ("Cast", `List [ src_ty; tgt_ty ]) ] -> - let* src_ty = integer_type_of_json src_ty in - let* tgt_ty = integer_type_of_json tgt_ty in - Ok (E.Cast (src_ty, tgt_ty)) - | _ -> Error ("unop_of_json failed on:" ^ show js) - -let binop_of_json (js : json) : (E.binop, string) result = +let cast_kind_of_json (js : json) : (cast_kind, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("Integer", `List [ src_ty; tgt_ty ]) ] -> + let* src_ty = integer_type_of_json src_ty in + let* tgt_ty = integer_type_of_json tgt_ty in + Ok (CastInteger (src_ty, tgt_ty)) + | _ -> Error "") + +let unop_of_json (js : json) : (unop, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `String "Not" -> Ok Not + | `String "Neg" -> Ok Neg + | `Assoc [ ("Cast", cast_kind) ] -> + let* cast_kind = cast_kind_of_json cast_kind in + Ok (Cast cast_kind) + | _ -> Error "") + +let binop_of_json (js : json) : (binop, string) result = match js with - | `String "BitXor" -> Ok E.BitXor - | `String "BitAnd" -> Ok E.BitAnd - | `String "BitOr" -> Ok E.BitOr - | `String "Eq" -> Ok E.Eq - | `String "Lt" -> Ok E.Lt - | `String "Le" -> Ok E.Le - | `String "Ne" -> Ok E.Ne - | `String "Ge" -> Ok E.Ge - | `String "Gt" -> Ok E.Gt - | `String "Div" -> Ok E.Div - | `String "Rem" -> Ok E.Rem - | `String "Add" -> Ok E.Add - | `String "Sub" -> Ok E.Sub - | `String "Mul" -> Ok E.Mul - | `String "Shl" -> Ok E.Shl - | `String "Shr" -> Ok E.Shr + | `String "BitXor" -> Ok BitXor + | `String "BitAnd" -> Ok BitAnd + | `String "BitOr" -> Ok BitOr + | `String "Eq" -> Ok Eq + | `String "Lt" -> Ok Lt + | `String "Le" -> Ok Le + | `String "Ne" -> Ok Ne + | `String "Ge" -> Ok Ge + | `String "Gt" -> Ok Gt + | `String "Div" -> Ok Div + | `String "Rem" -> Ok Rem + | `String "Add" -> Ok Add + | `String "Sub" -> Ok Sub + | `String "Mul" -> Ok Mul + | `String "Shl" -> Ok Shl + | `String "Shr" -> Ok Shr | _ -> Error ("binop_of_json failed on:" ^ show js) -let literal_of_json (js : json) : (PV.literal, string) result = +let literal_of_json (js : json) : (literal, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Scalar", scalar_value) ] -> let* scalar_value = scalar_value_of_json scalar_value in - Ok (PV.Scalar scalar_value) + Ok (VScalar scalar_value) | `Assoc [ ("Bool", v) ] -> let* v = bool_of_json v in - Ok (PV.Bool v) + Ok (VBool v) | `Assoc [ ("Char", v) ] -> let* v = char_of_json v in - Ok (PV.Char v) + Ok (VChar v) | _ -> Error "") -let operand_of_json (js : json) : (E.operand, string) result = +let assumed_fun_id_of_json (js : json) : (assumed_fun_id, string) result = + match js with + | `String "BoxNew" -> Ok BoxNew + | `String "BoxFree" -> Ok BoxFree + | `String "ArrayIndexShared" -> Ok ArrayIndexShared + | `String "ArrayIndexMut" -> Ok ArrayIndexMut + | `String "ArrayToSliceShared" -> Ok ArrayToSliceShared + | `String "ArrayToSliceMut" -> Ok ArrayToSliceMut + | `String "ArrayRepeat" -> Ok ArrayRepeat + | `String "SliceIndexShared" -> Ok SliceIndexShared + | `String "SliceIndexMut" -> Ok SliceIndexMut + | _ -> Error ("assumed_fun_id_of_json failed on:" ^ show js) + +let fun_id_of_json (js : json) : (fun_id, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("Regular", id) ] -> + let* id = FunDeclId.id_of_json id in + Ok (FRegular id) + | `Assoc [ ("Assumed", fid) ] -> + let* fid = assumed_fun_id_of_json fid in + Ok (FAssumed fid) + | _ -> Error "") + +let fun_id_or_trait_method_ref_of_json (js : json) : + (fun_id_or_trait_method_ref, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("Fun", id) ] -> + let* id = fun_id_of_json id in + Ok (FunId id) + | `Assoc [ ("Trait", `List [ trait_ref; method_name; fun_decl_id ]) ] -> + let* trait_ref = trait_ref_of_json trait_ref in + let* method_name = string_of_json method_name in + let* fun_decl_id = FunDeclId.id_of_json fun_decl_id in + Ok (TraitMethod (trait_ref, method_name, fun_decl_id)) + | _ -> Error "") + +let fn_ptr_of_json (js : json) : (fn_ptr, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("func", func); + ("generics", generics); + ("trait_and_method_generic_args", trait_and_method_generic_args); + ] -> + let* func = fun_id_or_trait_method_ref_of_json func in + let* generics = generic_args_of_json generics in + let* trait_and_method_generic_args = + option_of_json generic_args_of_json trait_and_method_generic_args + in + Ok { func; generics; trait_and_method_generic_args } + | _ -> Error "") + +let rec constant_expr_of_json (js : json) : (constant_expr, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("value", value); ("ty", ty) ] -> + let* value = raw_constant_expr_of_json value in + let* ty = ty_of_json ty in + Ok { value; ty } + | _ -> Error "") + +and raw_constant_expr_of_json (js : json) : (raw_constant_expr, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("Literal", lit) ] -> + let* lit = literal_of_json lit in + Ok (CLiteral lit) + | `Assoc [ ("Var", vid) ] -> + let* vid = ConstGenericVarId.id_of_json vid in + Ok (CVar vid) + | `Assoc [ ("TraitConst", `List [ trait_ref; generics; const_name ]) ] -> + let* trait_ref = trait_ref_of_json trait_ref in + let* generics = generic_args_of_json generics in + let* const_name = string_of_json const_name in + Ok (CTraitConst (trait_ref, generics, const_name)) + | `Assoc [ ("FnPtr", fn_ptr) ] -> + let* fn_ptr = fn_ptr_of_json fn_ptr in + Ok (CFnPtr fn_ptr) + | _ -> Error "") + +let operand_of_json (js : json) : (operand, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Copy", place) ] -> let* place = place_of_json place in - Ok (E.Copy place) + Ok (Copy place) | `Assoc [ ("Move", place) ] -> let* place = place_of_json place in - Ok (E.Move place) - | `Assoc [ ("Const", `List [ ty; cv ]) ] -> - let* ty = ety_of_json ty in - let* cv = literal_of_json cv in - Ok (E.Constant (ty, cv)) + Ok (Move place) + | `Assoc [ ("Const", cv) ] -> + let* cv = constant_expr_of_json cv in + Ok (Constant cv) | _ -> Error "") -let aggregate_kind_of_json (js : json) : (E.aggregate_kind, string) result = +let aggregate_kind_of_json (js : json) : (aggregate_kind, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `String "Tuple" -> Ok E.AggregatedTuple - | `Assoc [ ("Option", `List [ variant_id; ty ]) ] -> - let* variant_id = T.VariantId.id_of_json variant_id in - let* ty = ety_of_json ty in - Ok (E.AggregatedOption (variant_id, ty)) - | `Assoc [ ("Adt", `List [ id; opt_variant_id; regions; tys; cgs ]) ] -> - let* id = T.TypeDeclId.id_of_json id in + | `Assoc [ ("Adt", `List [ id; opt_variant_id; generics ]) ] -> + let* id = type_id_of_json id in let* opt_variant_id = - option_of_json T.VariantId.id_of_json opt_variant_id + option_of_json VariantId.id_of_json opt_variant_id in - let* regions = list_of_json erased_region_of_json regions in - let* tys = list_of_json ety_of_json tys in - let* cgs = list_of_json const_generic_of_json cgs in - Ok (E.AggregatedAdt (id, opt_variant_id, regions, tys, cgs)) - | `Assoc [ ("Range", ty) ] -> - let* ty = ety_of_json ty in - Ok (E.AggregatedRange ty) + let* generics = generic_args_of_json generics in + Ok (AggregatedAdt (id, opt_variant_id, generics)) | `Assoc [ ("Array", `List [ ty; cg ]) ] -> - let* ty = ety_of_json ty in + let* ty = ty_of_json ty in let* cg = const_generic_of_json cg in - Ok (E.AggregatedArray (ty, cg)) + Ok (AggregatedArray (ty, cg)) | _ -> Error "") -let rvalue_of_json (js : json) : (E.rvalue, string) result = +let rvalue_of_json (js : json) : (rvalue, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Use", op) ] -> let* op = operand_of_json op in - Ok (E.Use op) + Ok (Use op) | `Assoc [ ("Ref", `List [ place; borrow_kind ]) ] -> let* place = place_of_json place in let* borrow_kind = borrow_kind_of_json borrow_kind in - Ok (E.Ref (place, borrow_kind)) + Ok (RvRef (place, borrow_kind)) | `Assoc [ ("UnaryOp", `List [ unop; op ]) ] -> let* unop = unop_of_json unop in let* op = operand_of_json op in - Ok (E.UnaryOp (unop, op)) + Ok (UnaryOp (unop, op)) | `Assoc [ ("BinaryOp", `List [ binop; op1; op2 ]) ] -> let* binop = binop_of_json binop in let* op1 = operand_of_json op1 in let* op2 = operand_of_json op2 in - Ok (E.BinaryOp (binop, op1, op2)) + Ok (BinaryOp (binop, op1, op2)) | `Assoc [ ("Discriminant", place) ] -> let* place = place_of_json place in - Ok (E.Discriminant place) + Ok (Discriminant place) | `Assoc [ ("Global", gid) ] -> - let* gid = E.GlobalDeclId.id_of_json gid in - Ok (E.Global gid) + let* gid = GlobalDeclId.id_of_json gid in + Ok (Global gid : rvalue) | `Assoc [ ("Aggregate", `List [ aggregate_kind; ops ]) ] -> let* aggregate_kind = aggregate_kind_of_json aggregate_kind in let* ops = list_of_json operand_of_json ops in - Ok (E.Aggregate (aggregate_kind, ops)) - | _ -> Error "") - -let assumed_fun_id_of_json (js : json) : (A.assumed_fun_id, string) result = - match js with - | `String "Replace" -> Ok A.Replace - | `String "BoxNew" -> Ok A.BoxNew - | `String "BoxDeref" -> Ok A.BoxDeref - | `String "BoxDerefMut" -> Ok A.BoxDerefMut - | `String "BoxFree" -> Ok A.BoxFree - | `String "VecNew" -> Ok A.VecNew - | `String "VecPush" -> Ok A.VecPush - | `String "VecInsert" -> Ok A.VecInsert - | `String "VecLen" -> Ok A.VecLen - | `String "VecIndex" -> Ok A.VecIndex - | `String "VecIndexMut" -> Ok A.VecIndexMut - | `String "ArrayIndexShared" -> Ok A.ArrayIndexShared - | `String "ArrayIndexMut" -> Ok A.ArrayIndexMut - | `String "ArrayToSliceShared" -> Ok A.ArrayToSliceShared - | `String "ArrayToSliceMut" -> Ok A.ArrayToSliceMut - | `String "ArraySubsliceShared" -> Ok A.ArraySubsliceShared - | `String "ArraySubsliceMut" -> Ok A.ArraySubsliceMut - | `String "SliceLen" -> Ok A.SliceLen - | `String "SliceIndexShared" -> Ok A.SliceIndexShared - | `String "SliceIndexMut" -> Ok A.SliceIndexMut - | `String "SliceSubsliceShared" -> Ok A.SliceSubsliceShared - | `String "SliceSubsliceMut" -> Ok A.SliceSubsliceMut - | _ -> Error ("assumed_fun_id_of_json failed on:" ^ show js) - -let fun_id_of_json (js : json) : (A.fun_id, string) result = - combine_error_msgs js __FUNCTION__ - (match js with - | `Assoc [ ("Regular", id) ] -> - let* id = A.FunDeclId.id_of_json id in - Ok (A.Regular id) - | `Assoc [ ("Assumed", fid) ] -> - let* fid = assumed_fun_id_of_json fid in - Ok (A.Assumed fid) + Ok (Aggregate (aggregate_kind, ops)) | _ -> Error "") -let fun_sig_of_json (js : json) : (A.fun_sig, string) result = +let params_info_of_json (js : json) : (params_info, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ - ("region_params", region_params); - ("num_early_bound_regions", num_early_bound_regions); - ("type_params", type_params); - ("const_generic_params", const_generic_params); - ("inputs", inputs); - ("output", output); - ("regions_hierarchy", regions_hierarchy); + ("num_region_params", num_region_params); + ("num_type_params", num_type_params); + ("num_const_generic_params", num_const_generic_params); + ("num_trait_clauses", num_trait_clauses); + ("num_regions_outlive", num_regions_outlive); + ("num_types_outlive", num_types_outlive); + ("num_trait_type_constraints", num_trait_type_constraints); ] -> - let* region_params = list_of_json region_var_of_json region_params in - let* num_early_bound_regions = int_of_json num_early_bound_regions in - let* regions_hierarchy = region_var_groups_of_json regions_hierarchy in - let* type_params = list_of_json type_var_of_json type_params in - let* const_generic_params = - list_of_json const_generic_var_of_json const_generic_params + let* num_region_params = int_of_json num_region_params in + let* num_type_params = int_of_json num_type_params in + let* num_const_generic_params = int_of_json num_const_generic_params in + let* num_trait_clauses = int_of_json num_trait_clauses in + let* num_regions_outlive = int_of_json num_regions_outlive in + let* num_types_outlive = int_of_json num_types_outlive in + let* num_trait_type_constraints = + int_of_json num_trait_type_constraints in - let* inputs = list_of_json sty_of_json inputs in - let* output = sty_of_json output in Ok { - A.region_params; - num_early_bound_regions; - regions_hierarchy; - type_params; - const_generic_params; - inputs; - output; + num_region_params; + num_type_params; + num_const_generic_params; + num_trait_clauses; + num_regions_outlive; + num_types_outlive; + num_trait_type_constraints; } | _ -> Error "") -let call_of_json (js : json) : (A.call, string) result = +let fun_sig_of_json (id_to_file : id_to_file_map) (js : json) : + (fun_sig, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ - ("func", func); - ("region_args", region_args); - ("type_args", type_args); - ("const_generic_args", const_generic_args); - ("args", args); - ("dest", dest); + ("is_unsafe", is_unsafe); + ("generics", generics); + ("preds", preds); + ("parent_params_info", parent_params_info); + ("inputs", inputs); + ("output", output); ] -> - let* func = fun_id_of_json func in - let* region_args = list_of_json erased_region_of_json region_args in - let* type_args = list_of_json ety_of_json type_args in - let* const_generic_args = - list_of_json const_generic_of_json const_generic_args + let* is_unsafe = bool_of_json is_unsafe in + let* generics = generic_params_of_json id_to_file generics in + let* preds = predicates_of_json preds in + let* parent_params_info = + option_of_json params_info_of_json parent_params_info in + let* inputs = list_of_json ty_of_json inputs in + let* output = ty_of_json output in + Ok { is_unsafe; generics; preds; parent_params_info; inputs; output } + | _ -> Error "") + +let call_of_json (js : json) : (call, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc [ ("func", func); ("args", args); ("dest", dest) ] -> + let* func = fn_ptr_of_json func in let* args = list_of_json operand_of_json args in let* dest = place_of_json dest in - Ok { A.func; region_args; type_args; const_generic_args; args; dest } + Ok { func; args; dest } | _ -> Error "") let gexpr_body_of_json (body_of_json : json -> ('body, string) result) (id_to_file : id_to_file_map) (js : json) : - ('body A.gexpr_body, string) result = + ('body gexpr_body, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc @@ -729,111 +948,298 @@ let gexpr_body_of_json (body_of_json : json -> ('body, string) result) let* arg_count = int_of_json arg_count in let* locals = list_of_json var_of_json locals in let* body = body_of_json body in - Ok { A.meta; arg_count; locals; body } + Ok { meta; arg_count; locals; body } + | _ -> Error "") + +let fun_kind_of_json (js : json) : (fun_kind, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `String "Regular" -> Ok RegularKind + | `Assoc + [ + ( "TraitMethodImpl", + `Assoc + [ + ("impl_id", impl_id); + ("trait_id", trait_id); + ("method_name", method_name); + ("provided", provided); + ] ); + ] -> + let* impl_id = TraitImplId.id_of_json impl_id in + let* trait_id = TraitDeclId.id_of_json trait_id in + let* method_name = string_of_json method_name in + let* provided = bool_of_json provided in + Ok (TraitMethodImpl (impl_id, trait_id, method_name, provided)) + | `Assoc [ ("TraitMethodDecl", `List [ trait_id; item_name ]) ] -> + let* trait_id = TraitDeclId.id_of_json trait_id in + let* item_name = string_of_json item_name in + Ok (TraitMethodDecl (trait_id, item_name)) + | `Assoc [ ("TraitMethodProvided", `List [ trait_id; item_name ]) ] -> + let* trait_id = TraitDeclId.id_of_json trait_id in + let* item_name = string_of_json item_name in + Ok (TraitMethodProvided (trait_id, item_name)) | _ -> Error "") let gfun_decl_of_json (body_of_json : json -> ('body, string) result) - (id_to_file : id_to_file_map) (js : json) : - ('body A.gfun_decl, string) result = + (id_to_file : id_to_file_map) (js : json) : ('body gfun_decl, string) result + = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("def_id", def_id); ("meta", meta); + ("is_local", is_local); ("name", name); ("signature", signature); + ("kind", kind); ("body", body); ] -> - let* def_id = A.FunDeclId.id_of_json def_id in + let* def_id = FunDeclId.id_of_json def_id in let* meta = meta_of_json id_to_file meta in - let* name = fun_name_of_json name in - let* signature = fun_sig_of_json signature in + let* is_local = bool_of_json is_local in + let* name = name_of_json id_to_file name in + let* signature = fun_sig_of_json id_to_file signature in + let* kind = fun_kind_of_json kind in let* body = option_of_json (gexpr_body_of_json body_of_json id_to_file) body in Ok - { A.def_id; meta; name; signature; body; is_global_decl_body = false } + { + def_id; + meta; + is_local; + name; + signature; + kind; + body; + is_global_decl_body = false; + } | _ -> Error "") -(** Auxiliary definition, which we use only for deserialization purposes *) -type 'body gglobal_decl = { - def_id : A.GlobalDeclId.id; - meta : meta; - body : 'body A.gexpr_body option; - name : global_name; - ty : T.ety; -} -[@@deriving show] - let gglobal_decl_of_json (body_of_json : json -> ('body, string) result) (id_to_file : id_to_file_map) (js : json) : - ('body gglobal_decl, string) result = + ('body gexpr_body option gglobal_decl, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("def_id", def_id); ("meta", meta); + ("is_local", is_local); ("name", name); ("ty", ty); ("body", body); ] -> - let* global_id = A.GlobalDeclId.id_of_json def_id in + let* global_id = GlobalDeclId.id_of_json def_id in let* meta = meta_of_json id_to_file meta in - let* name = fun_name_of_json name in - let* ty = ety_of_json ty in + let* is_local = bool_of_json is_local in + let* name = name_of_json id_to_file name in + let* ty = ty_of_json ty in let* body = option_of_json (gexpr_body_of_json body_of_json id_to_file) body in - Ok { def_id = global_id; meta; body; name; ty } + let global = { def_id = global_id; meta; body; is_local; name; ty } in + Ok global + | _ -> Error "") + +let trait_decl_of_json (id_to_file : id_to_file_map) (js : json) : + (trait_decl, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("def_id", def_id); + ("is_local", is_local); + ("name", name); + ("meta", meta); + ("generics", generics); + ("preds", preds); + ("parent_clauses", parent_clauses); + ("consts", consts); + ("types", types); + ("required_methods", required_methods); + ("provided_methods", provided_methods); + ] -> + let* def_id = TraitDeclId.id_of_json def_id in + let* is_local = bool_of_json is_local in + let* name = name_of_json id_to_file name in + let* meta = meta_of_json id_to_file meta in + let* generics = generic_params_of_json id_to_file generics in + let* preds = predicates_of_json preds in + let* parent_clauses = + list_of_json (trait_clause_of_json id_to_file) parent_clauses + in + let* consts = + list_of_json + (pair_of_json string_of_json + (pair_of_json ty_of_json + (option_of_json GlobalDeclId.id_of_json))) + consts + in + let* types = + list_of_json + (pair_of_json string_of_json + (pair_of_json + (list_of_json (trait_clause_of_json id_to_file)) + (option_of_json ty_of_json))) + types + in + let* required_methods = + list_of_json + (pair_of_json string_of_json FunDeclId.id_of_json) + required_methods + in + let* provided_methods = + list_of_json + (pair_of_json string_of_json (option_of_json FunDeclId.id_of_json)) + provided_methods + in + Ok + { + def_id; + is_local; + name; + meta; + generics; + preds; + parent_clauses; + consts; + types; + required_methods; + provided_methods; + } + | _ -> Error "") + +let trait_impl_of_json (id_to_file : id_to_file_map) (js : json) : + (trait_impl, string) result = + combine_error_msgs js __FUNCTION__ + (match js with + | `Assoc + [ + ("def_id", def_id); + ("is_local", is_local); + ("name", name); + ("meta", meta); + ("impl_trait", impl_trait); + ("generics", generics); + ("preds", preds); + ("parent_trait_refs", parent_trait_refs); + ("consts", consts); + ("types", types); + ("required_methods", required_methods); + ("provided_methods", provided_methods); + ] -> + let* def_id = TraitImplId.id_of_json def_id in + let* is_local = bool_of_json is_local in + let* name = name_of_json id_to_file name in + let* meta = meta_of_json id_to_file meta in + let* impl_trait = trait_decl_ref_of_json impl_trait in + let* generics = generic_params_of_json id_to_file generics in + let* preds = predicates_of_json preds in + let* parent_trait_refs = + list_of_json trait_ref_of_json parent_trait_refs + in + let* consts = + list_of_json + (pair_of_json string_of_json + (pair_of_json ty_of_json GlobalDeclId.id_of_json)) + consts + in + let* types = + list_of_json + (pair_of_json string_of_json + (pair_of_json (list_of_json trait_ref_of_json) ty_of_json)) + types + in + let methods_of_json = + list_of_json (pair_of_json string_of_json FunDeclId.id_of_json) + in + let* required_methods = methods_of_json required_methods in + let* provided_methods = methods_of_json provided_methods in + Ok + ({ + def_id; + is_local; + name; + meta; + impl_trait; + generics; + preds; + parent_trait_refs; + consts; + types; + required_methods; + provided_methods; + } + : trait_impl) | _ -> Error "") let g_declaration_group_of_json (id_of_json : json -> ('id, string) result) - (js : json) : ('id A.g_declaration_group, string) result = + (js : json) : ('id g_declaration_group, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `Assoc [ ("NonRec", `List [ id ]) ] -> + | `Assoc [ ("NonRec", id) ] -> let* id = id_of_json id in - Ok (A.NonRec id) - | `Assoc [ ("Rec", `List [ ids ]) ] -> + Ok (NonRecGroup id) + | `Assoc [ ("Rec", ids) ] -> let* ids = list_of_json id_of_json ids in - Ok (A.Rec ids) + Ok (RecGroup ids) | _ -> Error "") let type_declaration_group_of_json (js : json) : - (A.type_declaration_group, string) result = + (type_declaration_group, string) result = combine_error_msgs js __FUNCTION__ - (g_declaration_group_of_json T.TypeDeclId.id_of_json js) + (g_declaration_group_of_json TypeDeclId.id_of_json js) let fun_declaration_group_of_json (js : json) : - (A.fun_declaration_group, string) result = + (fun_declaration_group, string) result = combine_error_msgs js __FUNCTION__ - (g_declaration_group_of_json A.FunDeclId.id_of_json js) + (g_declaration_group_of_json FunDeclId.id_of_json js) let global_declaration_group_of_json (js : json) : - (A.GlobalDeclId.id, string) result = + (GlobalDeclId.id, string) result = combine_error_msgs js __FUNCTION__ - (match js with - | `Assoc [ ("NonRec", `List [ id ]) ] -> - let* id = A.GlobalDeclId.id_of_json id in - Ok id - | `Assoc [ ("Rec", `List [ _ ]) ] -> Error "got mutually dependent globals" - | _ -> Error "") + (let* decl = g_declaration_group_of_json GlobalDeclId.id_of_json js in + match decl with + | NonRecGroup id -> Ok id + | RecGroup _ -> Error "got mutually dependent globals") -let declaration_group_of_json (js : json) : (A.declaration_group, string) result - = +let trait_declaration_group_of_json (js : json) : + (TraitDeclId.id, string) result = + combine_error_msgs js __FUNCTION__ + (let* decl = g_declaration_group_of_json TraitDeclId.id_of_json js in + match decl with + | NonRecGroup id -> Ok id + | RecGroup _ -> Error "got mutually dependent trait decls") + +let trait_implementation_group_of_json (js : json) : + (TraitImplId.id, string) result = + combine_error_msgs js __FUNCTION__ + (let* decl = g_declaration_group_of_json TraitImplId.id_of_json js in + match decl with + | NonRecGroup id -> Ok id + | RecGroup _ -> Error "got mutually dependent trait impls") + +let declaration_group_of_json (js : json) : (declaration_group, string) result = combine_error_msgs js __FUNCTION__ (match js with - | `Assoc [ ("Type", `List [ decl ]) ] -> + | `Assoc [ ("Type", decl) ] -> let* decl = type_declaration_group_of_json decl in - Ok (A.Type decl) - | `Assoc [ ("Fun", `List [ decl ]) ] -> + Ok (TypeGroup decl) + | `Assoc [ ("Fun", decl) ] -> let* decl = fun_declaration_group_of_json decl in - Ok (A.Fun decl) - | `Assoc [ ("Global", `List [ decl ]) ] -> + Ok (FunGroup decl) + | `Assoc [ ("Global", decl) ] -> let* id = global_declaration_group_of_json decl in - Ok (A.Global id) + Ok (GlobalGroup id) + | `Assoc [ ("TraitDecl", decl) ] -> + let* id = trait_declaration_group_of_json decl in + Ok (TraitDeclGroup id) + | `Assoc [ ("TraitImpl", decl) ] -> + let* id = trait_implementation_group_of_json decl in + Ok (TraitImplGroup id) | _ -> Error "") let length_of_json_list (js : json) : (int, string) result = diff --git a/charon-ml/src/GAstUtils.ml b/charon-ml/src/GAstUtils.ml index 8e059c52..7943350e 100644 --- a/charon-ml/src/GAstUtils.ml +++ b/charon-ml/src/GAstUtils.ml @@ -1,37 +1,37 @@ +open Types open GAst -module T = Types (** Small utility: list the transitive parents of a region var group. We don't do that in an efficient manner, but it doesn't matter. This list *doesn't* include the current region. *) -let rec list_ancestor_region_groups (sg : fun_sig) (gid : T.RegionGroupId.id) : - T.RegionGroupId.Set.t = - let rg = T.RegionGroupId.nth sg.regions_hierarchy gid in +let rec list_ancestor_region_groups (regions_hierarchy : region_groups) + (gid : RegionGroupId.id) : RegionGroupId.Set.t = + let rg = RegionGroupId.nth regions_hierarchy gid in let parents = List.fold_left (fun s gid -> (* Compute the parents *) - let parents = list_ancestor_region_groups sg gid in + let parents = list_ancestor_region_groups regions_hierarchy gid in (* Parents U current region *) - let parents = T.RegionGroupId.Set.add gid parents in + let parents = RegionGroupId.Set.add gid parents in (* Make the union with the accumulator *) - T.RegionGroupId.Set.union s parents) - T.RegionGroupId.Set.empty rg.parents + RegionGroupId.Set.union s parents) + RegionGroupId.Set.empty rg.parents in parents (** Small utility: same as {!list_ancestor_region_groups}, but returns an ordered list. *) -let list_ordered_ancestor_region_groups (sg : fun_sig) - (gid : T.RegionGroupId.id) : T.RegionGroupId.id list = - let pset = list_ancestor_region_groups sg gid in +let list_ordered_ancestor_region_groups (regions_hierarchy : region_groups) + (gid : RegionGroupId.id) : RegionGroupId.id list = + let pset = list_ancestor_region_groups regions_hierarchy gid in let parents = List.filter - (fun (rg : T.region_var_group) -> T.RegionGroupId.Set.mem rg.id pset) - sg.regions_hierarchy + (fun (rg : region_group) -> RegionGroupId.Set.mem rg.id pset) + regions_hierarchy in - let parents = List.map (fun (rg : T.region_var_group) -> rg.id) parents in + let parents = List.map (fun (rg : region_group) -> rg.id) parents in parents let gexpr_body_get_input_vars (fbody : 'body gexpr_body) : var list = @@ -45,16 +45,25 @@ let fun_body_get_input_vars (fbody : 'body gexpr_body) : var list = let split_declarations (decls : declaration_group list) : type_declaration_group list * fun_declaration_group list - * GlobalDeclId.id list = + * GlobalDeclId.id list + * TraitDeclId.id list + * TraitImplId.id list = let rec split decls = match decls with - | [] -> ([], [], []) + | [] -> ([], [], [], [], []) | d :: decls' -> ( - let types, funs, globals = split decls' in + let types, funs, globals, trait_decls, trait_impls = split decls' in match d with - | Type decl -> (decl :: types, funs, globals) - | Fun decl -> (types, decl :: funs, globals) - | Global decl -> (types, funs, decl :: globals)) + | TypeGroup decl -> + (decl :: types, funs, globals, trait_decls, trait_impls) + | FunGroup decl -> + (types, decl :: funs, globals, trait_decls, trait_impls) + | GlobalGroup decl -> + (types, funs, decl :: globals, trait_decls, trait_impls) + | TraitDeclGroup decl -> + (types, funs, globals, decl :: trait_decls, trait_impls) + | TraitImplGroup decl -> + (types, funs, globals, trait_decls, decl :: trait_impls)) in split decls @@ -62,24 +71,31 @@ let split_declarations (decls : declaration_group list) : declaration groups. *) let split_declarations_to_group_maps (decls : declaration_group list) : - type_declaration_group T.TypeDeclId.Map.t + type_declaration_group TypeDeclId.Map.t * fun_declaration_group FunDeclId.Map.t - * GlobalDeclId.Set.t = + * GlobalDeclId.Set.t + * TraitDeclId.Set.t + * TraitImplId.Set.t = let module G (M : Map.S) = struct let add_group (map : M.key g_declaration_group M.t) (group : M.key g_declaration_group) : M.key g_declaration_group M.t = match group with - | NonRec id -> M.add id group map - | Rec ids -> List.fold_left (fun map id -> M.add id group map) map ids + | NonRecGroup id -> M.add id group map + | RecGroup ids -> + List.fold_left (fun map id -> M.add id group map) map ids let create_map (groups : M.key g_declaration_group list) : M.key g_declaration_group M.t = List.fold_left add_group M.empty groups end in - let types, funs, globals = split_declarations decls in - let module TG = G (T.TypeDeclId.Map) in + let types, funs, globals, trait_decls, trait_impls = + split_declarations decls + in + let module TG = G (TypeDeclId.Map) in let types = TG.create_map types in let module FG = G (FunDeclId.Map) in let funs = FG.create_map funs in let globals = GlobalDeclId.Set.of_list globals in - (types, funs, globals) + let trait_decls = TraitDeclId.Set.of_list trait_decls in + let trait_impls = TraitImplId.Set.of_list trait_impls in + (types, funs, globals, trait_decls, trait_impls) diff --git a/charon-ml/src/LlbcAst.ml b/charon-ml/src/LlbcAst.ml index b753d1f8..634b8abc 100644 --- a/charon-ml/src/LlbcAst.ml +++ b/charon-ml/src/LlbcAst.ml @@ -1,9 +1,8 @@ include GAst open Types -open PrimitiveValues +open Values open Expressions open Meta -open Names type statement = { meta : meta; (** The statement meta-data *) @@ -71,14 +70,8 @@ and switch = type fun_body = statement gexpr_body [@@deriving show] type fun_decl = statement gfun_decl [@@deriving show] -type global_decl = { - meta : meta; - def_id : GlobalDeclId.id; - name : global_name; - ty : ety; - body_id : FunDeclId.id; (** TODO: this field should be an option *) -} -[@@deriving show] +(* TODO: the function id should be an option *) +type global_decl = FunDeclId.id gglobal_decl [@@deriving show] (** LLBC crate *) -type crate = (fun_decl, global_decl) gcrate +type crate = (statement, FunDeclId.id) gcrate diff --git a/charon-ml/src/LlbcAstUtils.ml b/charon-ml/src/LlbcAstUtils.ml index 296c28ed..223e87e7 100644 --- a/charon-ml/src/LlbcAstUtils.ml +++ b/charon-ml/src/LlbcAstUtils.ml @@ -71,9 +71,12 @@ let compute_fun_decl_groups_map (c : crate) : FunDeclId.Set.t FunDeclId.Map.t = (List.flatten (List.filter_map (function - | Fun (NonRec id) -> Some [ (id, FunDeclId.Set.singleton id) ] - | Fun (Rec ids) -> + | FunGroup (NonRecGroup id) -> + Some [ (id, FunDeclId.Set.singleton id) ] + | FunGroup (RecGroup ids) -> let idset = FunDeclId.Set.of_list ids in Some (List.map (fun id -> (id, idset)) ids) - | Type _ | Global _ -> None) + | TypeGroup _ | GlobalGroup _ | TraitDeclGroup _ | TraitImplGroup _ + -> + None) c.declarations)) diff --git a/charon-ml/src/LlbcOfJson.ml b/charon-ml/src/LlbcOfJson.ml index 2a78a75c..31bb1ccd 100644 --- a/charon-ml/src/LlbcOfJson.ml +++ b/charon-ml/src/LlbcOfJson.ml @@ -5,81 +5,82 @@ include GAstOfJson open OfJsonBasic -module A = LlbcAst +open Types +open LlbcAst -let assertion_of_json (js : json) : (A.assertion, string) result = +let assertion_of_json (js : json) : (assertion, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("cond", cond); ("expected", expected) ] -> let* cond = operand_of_json cond in let* expected = bool_of_json expected in - Ok { A.cond; expected } + Ok { cond; expected } | _ -> Error "") let rec statement_of_json (id_to_file : id_to_file_map) (js : json) : - (A.statement, string) result = + (statement, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("meta", meta); ("content", content) ] -> let* meta = meta_of_json id_to_file meta in let* content = raw_statement_of_json id_to_file content in - Ok { A.meta; content } + Ok { meta; content } | _ -> Error "") and raw_statement_of_json (id_to_file : id_to_file_map) (js : json) : - (A.raw_statement, string) result = + (raw_statement, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Assign", `List [ place; rvalue ]) ] -> let* place = place_of_json place in let* rvalue = rvalue_of_json rvalue in - Ok (A.Assign (place, rvalue)) + Ok (Assign (place, rvalue)) | `Assoc [ ("FakeRead", place) ] -> let* place = place_of_json place in - Ok (A.FakeRead place) + Ok (FakeRead place) | `Assoc [ ("SetDiscriminant", `List [ place; variant_id ]) ] -> let* place = place_of_json place in - let* variant_id = T.VariantId.id_of_json variant_id in - Ok (A.SetDiscriminant (place, variant_id)) + let* variant_id = VariantId.id_of_json variant_id in + Ok (SetDiscriminant (place, variant_id)) | `Assoc [ ("Drop", place) ] -> let* place = place_of_json place in - Ok (A.Drop place) + Ok (Drop place) | `Assoc [ ("Assert", assertion) ] -> let* assertion = assertion_of_json assertion in - Ok (A.Assert assertion) + Ok (Assert assertion) | `Assoc [ ("Call", call) ] -> let* call = call_of_json call in - Ok (A.Call call) - | `String "Panic" -> Ok A.Panic - | `String "Return" -> Ok A.Return + Ok (Call call) + | `String "Panic" -> Ok Panic + | `String "Return" -> Ok Return | `Assoc [ ("Break", i) ] -> let* i = int_of_json i in - Ok (A.Break i) + Ok (Break i) | `Assoc [ ("Continue", i) ] -> let* i = int_of_json i in - Ok (A.Continue i) - | `String "Nop" -> Ok A.Nop + Ok (Continue i) + | `String "Nop" -> Ok Nop | `Assoc [ ("Sequence", `List [ st1; st2 ]) ] -> let* st1 = statement_of_json id_to_file st1 in let* st2 = statement_of_json id_to_file st2 in - Ok (A.Sequence (st1, st2)) + Ok (Sequence (st1, st2)) | `Assoc [ ("Switch", tgt) ] -> let* switch = switch_of_json id_to_file tgt in - Ok (A.Switch switch) + Ok (Switch switch) | `Assoc [ ("Loop", st) ] -> let* st = statement_of_json id_to_file st in - Ok (A.Loop st) + Ok (Loop st) | _ -> Error "") and switch_of_json (id_to_file : id_to_file_map) (js : json) : - (A.switch, string) result = + (switch, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("If", `List [ op; st1; st2 ]) ] -> let* op = operand_of_json op in let* st1 = statement_of_json id_to_file st1 in let* st2 = statement_of_json id_to_file st2 in - Ok (A.If (op, st1, st2)) + Ok (If (op, st1, st2)) | `Assoc [ ("SwitchInt", `List [ op; int_ty; tgts; otherwise ]) ] -> let* op = operand_of_json op in let* int_ty = integer_type_of_json int_ty in @@ -91,22 +92,22 @@ and switch_of_json (id_to_file : id_to_file_map) (js : json) : tgts in let* otherwise = statement_of_json id_to_file otherwise in - Ok (A.SwitchInt (op, int_ty, tgts, otherwise)) + Ok (SwitchInt (op, int_ty, tgts, otherwise)) | `Assoc [ ("Match", `List [ p; tgts; otherwise ]) ] -> let* p = place_of_json p in let* tgts = list_of_json (pair_of_json - (list_of_json T.VariantId.id_of_json) + (list_of_json VariantId.id_of_json) (statement_of_json id_to_file)) tgts in let* otherwise = statement_of_json id_to_file otherwise in - Ok (A.Match (p, tgts, otherwise)) + Ok (Match (p, tgts, otherwise)) | _ -> Error "") let fun_decl_of_json (id_to_file : id_to_file_map) (js : json) : - (A.fun_decl, string) result = + (fun_decl, string) result = combine_error_msgs js __FUNCTION__ (gfun_decl_of_json (statement_of_json id_to_file) id_to_file js) @@ -117,47 +118,52 @@ type global_id_converter = { fun_count : int } [@@deriving show] To do so, it adds the global id to the number of function declarations : We have the bijection [global_fun_id <=> global_id + fun_id_count]. *) -let global_to_fun_id (conv : global_id_converter) (gid : A.GlobalDeclId.id) : - A.FunDeclId.id = - A.FunDeclId.of_int (A.GlobalDeclId.to_int gid + conv.fun_count) +let global_to_fun_id (conv : global_id_converter) (gid : GlobalDeclId.id) : + FunDeclId.id = + FunDeclId.of_int (GlobalDeclId.to_int gid + conv.fun_count) (** Deserialize a global declaration, and decompose it into a global declaration and a function declaration. *) let global_decl_of_json (id_to_file : id_to_file_map) (js : json) - (gid_conv : global_id_converter) : - (A.global_decl * A.fun_decl, string) result = + (gid_conv : global_id_converter) : (global_decl * fun_decl, string) result = combine_error_msgs js __FUNCTION__ ((* Deserialize the global declaration *) let* global = gglobal_decl_of_json (statement_of_json id_to_file) id_to_file js in - let { def_id = global_id; meta; body; name; ty } = global in + let { def_id = global_id; meta; body; is_local; name; ty } = global in (* Decompose into a global and a function *) let fun_id = global_to_fun_id gid_conv global.def_id in - let signature : A.fun_sig = + let signature : fun_sig = { - region_params = []; - num_early_bound_regions = 0; - regions_hierarchy = []; - type_params = []; - const_generic_params = []; + (* Not sure about `is_unsafe` actually *) + is_unsafe = false; + generics = TypesUtils.empty_generic_params; + preds = TypesUtils.empty_predicates; + parent_params_info = None; inputs = []; - output = TU.ety_no_regions_to_sty ty; + output = ty; } in - Ok - ( { A.def_id = global_id; meta; body_id = fun_id; name; ty }, - { - A.def_id = fun_id; - meta; - name; - signature; - body; - is_global_decl_body = true; - } )) + let global_decl : global_decl = + { def_id = global_id; meta; body = fun_id; is_local; name; ty } + in + let fun_decl : fun_decl = + { + def_id = fun_id; + meta; + is_local; + name; + signature; + kind = RegularKind; + body; + is_global_decl_body = true; + } + in + Ok (global_decl, fun_decl)) -let crate_of_json (js : json) : (A.crate, string) result = +let crate_of_json (js : json) : (crate, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc @@ -168,6 +174,8 @@ let crate_of_json (js : json) : (A.crate, string) result = ("types", types); ("functions", functions); ("globals", globals); + ("trait_decls", trait_decls); + ("trait_impls", trait_impls); ] -> (* We first deserialize the declaration groups (which simply contain ids) * and all the declarations *butù* the globals *) @@ -190,20 +198,44 @@ let crate_of_json (js : json) : (A.crate, string) result = globals in let globals, global_bodies = List.split globals in - let types = - T.TypeDeclId.Map.of_list - (List.map (fun (d : T.type_decl) -> (d.def_id, d)) types) + let type_decls = + TypeDeclId.Map.of_list + (List.map (fun (d : type_decl) -> (d.def_id, d)) types) in (* Concatenate the functions and the global bodies *) - let functions = - A.FunDeclId.Map.of_list + let fun_decls = + FunDeclId.Map.of_list (List.map - (fun (d : A.fun_decl) -> (d.def_id, d)) + (fun (d : fun_decl) -> (d.def_id, d)) (functions @ global_bodies)) in - let globals = - A.GlobalDeclId.Map.of_list - (List.map (fun (d : A.global_decl) -> (d.def_id, d)) globals) + let global_decls = + GlobalDeclId.Map.of_list + (List.map (fun (d : global_decl) -> (d.def_id, d)) globals) + in + (* Traits *) + let* trait_decls = + list_of_json (trait_decl_of_json id_to_file) trait_decls + in + let* trait_impls = + list_of_json (trait_impl_of_json id_to_file) trait_impls + in + let trait_decls = + TraitDeclId.Map.of_list + (List.map (fun (d : trait_decl) -> (d.def_id, d)) trait_decls) + in + let trait_impls = + TraitImplId.Map.of_list + (List.map (fun (d : trait_impl) -> (d.def_id, d)) trait_impls) in - Ok { A.name; declarations; types; functions; globals } + Ok + { + name; + declarations; + type_decls; + fun_decls; + global_decls; + trait_decls; + trait_impls; + } | _ -> Error "") diff --git a/charon-ml/src/Meta.ml b/charon-ml/src/Meta.ml index f0e4ca04..b14f4dd9 100644 --- a/charon-ml/src/Meta.ml +++ b/charon-ml/src/Meta.ml @@ -5,16 +5,17 @@ type loc = { line : int; (** The (1-based) line number. *) col : int; (** The (0-based) column offset. *) } -[@@deriving show] +[@@deriving show, ord] type file_name = | Virtual of string (** A remapped path (namely paths into stdlib) *) | Local of string (** A local path (a file coming from the current crate for instance) *) -[@@deriving show] +[@@deriving show, ord] (** Span data *) -type span = { file : file_name; beg_loc : loc; end_loc : loc } [@@deriving show] +type span = { file : file_name; beg_loc : loc; end_loc : loc } +[@@deriving show, ord] type meta = { span : span; @@ -41,4 +42,4 @@ type meta = { generated_from_span : span option; (** Where the code actually comes from, in case of macro expansion/inlining/etc. *) } -[@@deriving show] +[@@deriving show, ord] diff --git a/charon-ml/src/NameMatcher.ml b/charon-ml/src/NameMatcher.ml new file mode 100644 index 00000000..00a1b278 --- /dev/null +++ b/charon-ml/src/NameMatcher.ml @@ -0,0 +1,936 @@ +(** Utilities to identify Rust definitions by matching on their names. + + Identifying Rust definitions is non trivial because of: + - the impl blocks, which are identified by their types + - trait instances, which don't have a name (and which we identify + with trait references) + + For this reason, we define: + - a small pattern matching language for Rust names + - a parser for this language + - matchers which check if a name matches a pattern + - helpers to derive patterns from names (useful when one identifies + some external functions that need custom treatment, as it avoids + writing patterns by hand) + + Here are some examples of patterns: + - ["core::mem::replace"]: the function [core::mem::replace] + - ["alloc::vec::{alloc::vec::Vec<@>}::push"]: the function [push] in any + impl block of type [alloc::vec::Vec], where T is a type variable. + Note that ["@"] means that this matches any (type) variable. In case + we need stronger constraints, we can name those variables: "@T". All the + occurrences of ["@T"] must match the same variable (ex.: ["Foo<@T, @T>"] + would match [Foo] but not [Foo]). + - the ["@"] syntax is used both for types and const generics. For regions/lifetimes, + we use ["'"]: ["&'a mut @T"] + - for the types we put inside blocks, we have syntax for arrays, slices, + and references: + - ["[@T; @N]"]: slice + - ["&'R mut @T"]: mutable reference + + Remark: [Box] is treated as a primitive type, which means that one only + needs to type ["Box"] (instead of ["alloc::boxed::Box"] - though the latter + also works). + *) + +(* The "raw" name matcher patterns *) +include Name_matcher_parser.Ast +include Name_matcher_parser.Interface +module T = Types + +(* + * Match a name + *) + +module VarOrderedType : Collections.OrderedType with type t = var = struct + type t = var + + let compare = compare_var + let to_string x = show_var x + let pp_t fmt x = Format.pp_print_string fmt (show_var x) + let show_t x = show_var x +end + +module VarMap = Collections.MakeMap (VarOrderedType) + +(* Context to lookup definitions *) +type ctx = { + type_decls : T.type_decl T.TypeDeclId.Map.t; + global_decls : LlbcAst.global_decl T.GlobalDeclId.Map.t; + trait_decls : GAst.trait_decl T.TraitDeclId.Map.t; +} + +(** Match configuration *) +type match_config = { + map_vars_to_vars : bool; + (** If true, only allow matching variables to variables. + + This is important when matching names: if the pattern + is [alloc::boxed::{Box<@T>}::new], we only want to match + names where [@T] is a variable. For instance, we wouldn't + want to match [alloc::boxed::{Box}::new] (if it existed...). + However, we might want to match instantiations (i.e., for which + [@T] is matched to [usize]) when matching function calls inside + bodies. + *) +} + +(** Mapped expressions. + + The {!MRegion} variant is used when matching generics. + *) +type mexpr = MTy of T.ty | MCg of T.const_generic | MRegion of T.region + +(* Small helper to store the mappings from variables to expressions *) +type maps = { + rmap : T.region VarMap.t ref; (** Regions map *) + vmap : mexpr VarMap.t ref; + (** Variables map (accounts both for the types and const generics) *) +} + +let mk_empty_maps () = { rmap = ref VarMap.empty; vmap = ref VarMap.empty } + +(** Update a map and check that there are no incompatible + constraints at the same time. *) +let update_map (find_opt : 'a -> 'm -> 'b option) (add : 'a -> 'b -> 'm -> 'm) + (m : 'm ref) (id : 'a) (v : 'b) : bool = + match find_opt id !m with + | None -> + (* Simply update *) + m := add id v !m; + true + | Some v' -> + (* Check the binding *) + v = v' + +let update_rmap (c : match_config) (m : maps) (id : var) (v : T.region) : bool = + let is_var = match v with RVar _ -> true | _ -> false in + if c.map_vars_to_vars && not is_var then false + else update_map VarMap.find_opt VarMap.add m.rmap id v + +let update_tmap (c : match_config) (m : maps) (id : var) (v : T.ty) : bool = + let is_var = match v with TVar _ -> true | _ -> false in + if c.map_vars_to_vars && not is_var then false + else update_map VarMap.find_opt VarMap.add m.vmap id (MTy v) + +let update_cmap (c : match_config) (m : maps) (id : var) (v : T.const_generic) : + bool = + let is_var = match v with CgVar _ -> true | _ -> false in + if c.map_vars_to_vars && not is_var then false + else update_map VarMap.find_opt VarMap.add m.vmap id (MCg v) + +let opt_update_rmap (c : match_config) (m : maps) (id : var option) + (v : T.region) : bool = + match id with None -> true | Some id -> update_rmap c m id v + +let opt_update_tmap (c : match_config) (m : maps) (id : var option) (v : T.ty) : + bool = + match id with None -> true | Some id -> update_tmap c m id v + +let opt_update_cmap (c : match_config) (m : maps) (id : var option) + (v : T.const_generic) : bool = + match id with None -> true | Some id -> update_cmap c m id v + +(** Pay attention when updating the names because we use this function + for several purposes: + - to match patterns with literal types + - to convert patterns to strings which can be parsed as patterns + - to convert patterns to string for printing/name generation + *) +let literal_type_to_string (ty : T.literal_type) : string = + match ty with + | TBool -> "bool" + | TChar -> "char" + | TInteger ty -> ( + match ty with + | Isize -> "isize" + | I8 -> "i8" + | I16 -> "i16" + | I32 -> "i32" + | I64 -> "i64" + | I128 -> "i128" + | Usize -> "usize" + | U8 -> "u8" + | U16 -> "u16" + | U32 -> "u32" + | U64 -> "u64" + | U128 -> "u128") + +(** Match a pattern with a region. + + Region true and update the maps if the match is successful, return false + otherwise. *) +let match_region (c : match_config) (m : maps) (id : region) (v : T.region) : + bool = + match (id, v) with + | RStatic, RStatic -> true + | RVar id, RVar _ -> opt_update_rmap c m id v + | RVar id, RStatic -> + if c.map_vars_to_vars then false else opt_update_rmap c m id v + | _ -> false + +let match_ref_kind (prk : ref_kind) (rk : T.ref_kind) : bool = + match (prk, rk) with RMut, RMut | RShared, RShared -> true | _ -> false + +let match_literal (pl : literal) (l : Values.literal) : bool = + match (pl, l) with + | LInt pv, VScalar v -> pv = v.value + | LBool pv, VBool v -> pv = v + | LChar pv, VChar v -> pv = v + | _ -> false + +let rec match_name_with_generics (ctx : ctx) (c : match_config) (p : pattern) + (n : T.name) (g : T.generic_args) : bool = + match (p, n) with + | [], [] -> + raise + (Failure + "match_name_with_generics: attempt to match empty names and patterns") + (* We shouldn't get there: the names/patterns should be non empty *) + | [ PIdent (pid, pg) ], [ PeIdent (id, _) ] -> + (* We reached the end: match the generics. + We have to generate an empty map. *) + pid = id && match_generic_args ctx c (mk_empty_maps ()) pg g + | [ PImpl pty ], [ PeImpl impl ] -> + (* We can get there when matching a prefix of the name with a pattern *) + match_expr_with_ty ctx c (mk_empty_maps ()) pty impl.ty + && g = TypesUtils.empty_generic_args + | PIdent (pid, pg) :: p, PeIdent (id, _) :: n -> + (* This is not the end: check that the generics are empty *) + pid = id && pg = [] && match_name_with_generics ctx c p n g + | PImpl pty :: p, PeImpl impl :: n -> + match_expr_with_ty ctx c (mk_empty_maps ()) pty impl.ty + && match_name_with_generics ctx c p n g + | _ -> false + +and match_name (ctx : ctx) (c : match_config) (p : pattern) (n : T.name) : bool + = + match_name_with_generics ctx c p n TypesUtils.empty_generic_args + +and match_pattern_with_type_id (ctx : ctx) (c : match_config) (m : maps) + (pid : pattern) (id : T.type_id) (generics : T.generic_args) : bool = + match id with + | TAdtId id -> + (* Lookup the type decl and match the name *) + let d = T.TypeDeclId.Map.find id ctx.type_decls in + match_name_with_generics ctx c pid d.name generics + | TTuple -> false + | TAssumed id -> ( + match (id, pid) with + | ( TBox, + ( [ PIdent ("Box", pgenerics) ] + | [ + PIdent ("alloc", []); + PIdent ("boxed", []); + PIdent ("Box", pgenerics); + ] ) ) -> + match_generic_args ctx c m pgenerics generics + | TStr, [ PIdent ("str", []) ] -> generics = TypesUtils.empty_generic_args + | _ -> false) + +and match_pattern_with_literal_type (pty : pattern) (ty : T.literal_type) : bool + = + let ty = literal_type_to_string ty in + pty = [ PIdent (ty, []) ] + +and match_primitive_adt (pid : primitive_adt) (id : T.type_id) : bool = + match (pid, id) with + | TTuple, TTuple | TArray, TAssumed TArray | TSlice, TAssumed TSlice -> true + | _ -> false + +and match_expr_with_ty (ctx : ctx) (c : match_config) (m : maps) (pty : expr) + (ty : T.ty) : bool = + match (pty, ty) with + | EComp pid, TAdt (id, generics) -> + match_pattern_with_type_id ctx c m pid id generics + | EComp pid, TLiteral lit -> match_pattern_with_literal_type pid lit + | EPrimAdt (pid, pgenerics), TAdt (id, generics) -> + match_primitive_adt pid id + && match_generic_args ctx c m pgenerics generics + | ERef (pr, pty, prk), TRef (r, ty, rk) -> + match_region c m pr r + && match_expr_with_ty ctx c m pty ty + && match_ref_kind prk rk + | EVar v, _ -> opt_update_tmap c m v ty + | EComp pid, TTraitType (trait_ref, generics, type_name) -> + generics = TypesUtils.empty_generic_args + && match_trait_type ctx c pid trait_ref type_name + | _ -> false + +and match_trait_type (ctx : ctx) (c : match_config) (pid : pattern) + (tr : T.trait_ref) (type_name : string) : bool = + (* We match the trait decl ref *) + (* We split the pattern between the trait decl ref and the associated type name *) + let pid, ptype_name = Collections.List.pop_last pid in + (* Lookup the trait declaration *) + let d = + T.TraitDeclId.Map.find tr.trait_decl_ref.trait_decl_id ctx.trait_decls + in + (* Match the trait decl ref *) + match_name_with_generics ctx c pid d.name tr.trait_decl_ref.decl_generics + && + (* Match the type name *) + match ptype_name with + | PIdent (ptype_name, []) -> ptype_name = type_name + | _ -> false + +and match_generic_args (ctx : ctx) (c : match_config) (m : maps) + (pgenerics : generic_args) (generics : T.generic_args) : bool = + let { regions; types; const_generics; trait_refs = _ } : T.generic_args = + generics + in + let generics = + List.concat + [ + List.map (fun x -> MRegion x) regions; + List.map (fun x -> MTy x) types; + List.map (fun x -> MCg x) const_generics; + ] + in + if List.length pgenerics = List.length generics then + List.for_all2 (match_generic_arg ctx c m) pgenerics generics + else false + +and match_generic_arg (ctx : ctx) (c : match_config) (m : maps) + (pg : generic_arg) (g : mexpr) : bool = + match (pg, g) with + | GRegion pr, MRegion r -> match_region c m pr r + | GExpr e, MTy ty -> match_expr_with_ty ctx c m e ty + | GExpr e, MCg cg -> match_expr_with_const_generic ctx c m e cg + | GValue v, MCg (CgValue cg) -> match_literal v cg + | _ -> false + +and match_expr_with_const_generic (ctx : ctx) (c : match_config) (m : maps) + (pcg : expr) (cg : T.const_generic) : bool = + match (pcg, cg) with + | EVar pv, _ -> opt_update_cmap c m pv cg + | EComp pat, CgGlobal gid -> + (* Lookup the decl and match the name *) + let d = T.GlobalDeclId.Map.find gid ctx.global_decls in + match_name ctx c pat d.name + | _ -> false + +let mk_name_with_generics_matcher (ctx : ctx) (c : match_config) (pat : string) + : T.name -> T.generic_args -> bool = + let pat = parse_pattern pat in + match_name_with_generics ctx c pat + +let mk_name_matcher (ctx : ctx) (c : match_config) (pat : string) : + T.name -> bool = + let pat = parse_pattern pat in + match_name ctx c pat + +(* + * Helpers to convert names to patterns + *) + +(* We use this to store the constraints maps (the map from variable + ids to option pattern variable ids) *) +type constraints = { + rmap : var option T.RegionId.Map.t; + tmap : var option T.TypeVarId.Map.t; + cmap : var option T.ConstGenericVarId.Map.t; +} + +let ref_kind_to_pattern (rk : T.ref_kind) : ref_kind = + match rk with RMut -> RMut | RShared -> RShared + +let region_to_pattern (m : constraints) (r : T.region) : region = + match r with + | RVar r -> RVar (T.RegionId.Map.find r m.rmap) + | RStatic -> RStatic + | _ -> raise (Failure "Unexpected") + +let type_var_to_pattern (m : constraints) (v : T.TypeVarId.id) : var option = + T.TypeVarId.Map.find v m.tmap + +let compute_constraints_map (generics : T.generic_params) : constraints = + let fresh_id (gen : int ref) : int = + let id = !gen in + gen := id + 1; + id + in + let rmap = + let rid_gen = ref 0 in + T.RegionId.Map.of_list + (List.map + (fun (r : T.region_var) -> + let v = + match r.name with + | None -> VarIndex (fresh_id rid_gen) + | Some name -> VarName name + in + (r.index, Some v)) + generics.regions) + in + let tmap = + T.TypeVarId.Map.of_list + (List.map + (fun (x : T.type_var) -> (x.index, Some (VarName x.name))) + generics.types) + in + let cmap = + T.ConstGenericVarId.Map.of_list + (List.map + (fun (x : T.const_generic_var) -> (x.index, Some (VarName x.name))) + generics.const_generics) + in + { rmap; tmap; cmap } + +type target_kind = + | TkPattern (** Generate a string which can be parsed as a pattern *) + | TkPretty (** Pretty printing *) + | TkName (** A name for code extraction (for instance for trait instances) *) + +type to_pat_config = { tgt : target_kind } + +let literal_type_to_pattern (c : to_pat_config) (lit : T.literal_type) : expr = + let lit = literal_type_to_string lit in + let lit = + match c.tgt with + | TkPattern | TkPretty -> lit + | TkName -> StringUtils.capitalize_first_letter lit + in + EComp [ PIdent (lit, []) ] + +let literal_to_pattern (_c : to_pat_config) (lit : Values.literal) : literal = + match lit with + | VScalar sv -> LInt sv.value + | VBool v -> LBool v + | VChar v -> LChar v + +let rec name_with_generic_args_to_pattern_aux (ctx : ctx) (c : to_pat_config) + (n : T.name) (generics : generic_args option) : pattern = + match n with + | [] -> raise (Failure "Empty names are not valid") + | [ e ] -> [ path_elem_with_generic_args_to_pattern ctx c e generics ] + | e :: n -> + path_elem_with_generic_args_to_pattern ctx c e None + :: name_with_generic_args_to_pattern_aux ctx c n generics + +and name_to_pattern_aux (ctx : ctx) (c : to_pat_config) (n : T.name) : pattern = + name_with_generic_args_to_pattern_aux ctx c n None + +and path_elem_with_generic_args_to_pattern (ctx : ctx) (c : to_pat_config) + (e : T.path_elem) (generics : generic_args option) : pattern_elem = + match e with + | PeIdent (s, _) -> ( + match generics with + | None -> PIdent (s, []) + | Some args -> PIdent (s, args)) + | PeImpl impl -> impl_elem_to_pattern ctx c impl + +and impl_elem_to_pattern (ctx : ctx) (c : to_pat_config) (impl : T.impl_elem) : + pattern_elem = + PImpl (ty_to_pattern ctx c impl.generics impl.ty) + +and ty_to_pattern_aux (ctx : ctx) (c : to_pat_config) (m : constraints) + (ty : T.ty) : expr = + match ty with + | TAdt (id, generics) -> ( + let generics = generic_args_to_pattern ctx c m generics in + match id with + | TAdtId id -> + (* Lookup the declaration *) + let d = T.TypeDeclId.Map.find id ctx.type_decls in + EComp + (name_with_generic_args_to_pattern_aux ctx c d.name (Some generics)) + | TTuple -> EPrimAdt (TTuple, generics) + | TAssumed TArray -> EPrimAdt (TArray, generics) + | TAssumed TSlice -> EPrimAdt (TSlice, generics) + | TAssumed TBox -> EComp [ PIdent ("Box", generics) ] + | TAssumed TStr -> EComp [ PIdent ("str", generics) ]) + | TVar v -> EVar (type_var_to_pattern m v) + | TLiteral lit -> literal_type_to_pattern c lit + | TRef (r, ty, rk) -> + ERef + ( region_to_pattern m r, + ty_to_pattern_aux ctx c m ty, + ref_kind_to_pattern rk ) + | TTraitType (trait_ref, generics, type_name) -> + assert (generics = TypesUtils.empty_generic_args); + let trait_decl_ref = trait_ref.trait_decl_ref in + let d = + T.TraitDeclId.Map.find trait_decl_ref.trait_decl_id ctx.trait_decls + in + let g = generic_args_to_pattern ctx c m trait_decl_ref.decl_generics in + let name = name_with_generic_args_to_pattern_aux ctx c d.name (Some g) in + let name = name @ [ PIdent (type_name, []) ] in + EComp name + | TNever | TRawPtr _ | TArrow _ -> raise (Failure "Unimplemented") + +and ty_to_pattern (ctx : ctx) (c : to_pat_config) (params : T.generic_params) + (ty : T.ty) : expr = + (* Compute the constraints map *) + let m = compute_constraints_map params in + (* Convert the type *) + ty_to_pattern_aux ctx c m ty + +and const_generic_to_pattern (ctx : ctx) (c : to_pat_config) (m : constraints) + (cg : T.const_generic) : generic_arg = + match cg with + | CgVar v -> GExpr (EVar (T.ConstGenericVarId.Map.find v m.cmap)) + | CgValue v -> GValue (literal_to_pattern c v) + | CgGlobal gid -> + let d = T.GlobalDeclId.Map.find gid ctx.global_decls in + let n = name_to_pattern_aux ctx c d.name in + GExpr (EComp n) + +and generic_args_to_pattern (ctx : ctx) (c : to_pat_config) (m : constraints) + (generics : T.generic_args) : generic_args = + let { regions; types; const_generics; trait_refs = _ } : T.generic_args = + generics + in + let regions = List.map (region_to_pattern m) regions in + let types = List.map (ty_to_pattern_aux ctx c m) types in + let const_generics = + List.map (const_generic_to_pattern ctx c m) const_generics + in + List.concat + [ + List.map (fun x -> GRegion x) regions; + List.map (fun x -> GExpr x) types; + const_generics; + ] + +let name_to_pattern (ctx : ctx) (c : to_pat_config) (n : T.name) : pattern = + (* Convert the name to a pattern *) + let pat = name_to_pattern_aux ctx c n in + (* Sanity check: the name should match the pattern *) + assert (c.tgt = TkName || match_name ctx { map_vars_to_vars = true } pat n); + (* Return *) + pat + +let name_with_generics_to_pattern (ctx : ctx) (c : to_pat_config) (n : T.name) + (params : T.generic_params) (args : T.generic_args) : pattern = + (* Convert the name to a pattern *) + let pat = + let m = compute_constraints_map params in + let args = generic_args_to_pattern ctx c m args in + name_with_generic_args_to_pattern_aux ctx c n (Some args) + in + (* Sanity check: the name should match the pattern *) + assert ( + c.tgt = TkName + || match_name_with_generics ctx { map_vars_to_vars = true } pat n args); + (* Return *) + pat + +(* + * Convert patterns to strings + *) +type print_config = { tgt : target_kind } + +let literal_to_string (c : print_config) (l : literal) : string = + match l with + | LInt v -> Z.to_string v + | LBool b -> Bool.to_string b + | LChar x -> ( + match c.tgt with + | TkPattern -> + (* TODO: we can't use the syntax 'x' for now because of lifetimes *) + raise (Failure "TODO") + | TkPretty -> "'" ^ String.make 1 x ^ "'" + | TkName -> String.make 1 x) + +let region_var_to_string (c : print_config) (v : var option) : string = + match c.tgt with + | TkPattern | TkPretty -> ( + match v with + | None -> "'_" + | Some (VarName n) -> "'" ^ n + | Some (VarIndex id) -> "'" ^ string_of_int id) + | TkName -> ( + match v with + | None -> "" + | Some (VarName n) -> StringUtils.capitalize_first_letter n + | Some (VarIndex id) -> string_of_int id) + +let region_to_string (c : print_config) (r : region) : string = + match r with + | RStatic -> ( + match c.tgt with TkPattern | TkPretty -> "'static" | TkName -> "Static") + | RVar v -> region_var_to_string c v + +let opt_var_to_string (c : print_config) (v : var option) : string = + match c.tgt with + | TkPattern -> ( + match v with + | None -> "@" + | Some (VarName n) -> "@" ^ n + | Some (VarIndex id) -> "@" ^ string_of_int id) + | TkPretty | TkName -> ( + (* Below: when generating names, we shouldn't use the None or VarIndex cases *) + match v with + | None -> "P" + | Some (VarName n) -> n + | Some (VarIndex id) -> "P" ^ string_of_int id) + +let rec pattern_to_string (c : print_config) (p : pattern) : string = + let sep = match c.tgt with TkPattern | TkPretty -> "::" | TkName -> "" in + String.concat sep (List.map (pattern_elem_to_string c) p) + +and pattern_elem_to_string (c : print_config) (e : pattern_elem) : string = + match e with + | PIdent (s, g) -> s ^ generic_args_to_string c g + | PImpl ty -> ( + let ty = expr_to_string c ty in + match c.tgt with TkPattern | TkPretty -> "{" ^ ty ^ "}" | TkName -> ty) + +and expr_to_string (c : print_config) (e : expr) : string = + match e with + | EComp pat -> pattern_to_string c pat + | EPrimAdt (id, generics) -> ( + match id with + | TTuple -> ( + let generics = List.map (generic_arg_to_string c) generics in + match c.tgt with + | TkPattern | TkPretty -> "(" ^ String.concat ", " generics ^ ")" + | TkName -> "Tuple" ^ String.concat "" generics) + | TArray -> ( + match generics with + | [ ty; cg ] -> ( + let ty = generic_arg_to_string c ty in + let cg = generic_arg_to_string c cg in + match c.tgt with + | TkPattern | TkPretty -> "[" ^ ty ^ "; " ^ cg ^ "]" + | TkName -> "Array" ^ ty ^ cg) + | _ -> raise (Failure "Ill-formed pattern")) + | TSlice -> ( + match generics with + | [ ty ] -> ( + let ty = generic_arg_to_string c ty in + match c.tgt with + | TkPattern | TkPretty -> "[" ^ ty ^ "]" + | TkName -> "Slice" ^ ty) + | _ -> raise (Failure "Ill-formed pattern"))) + | ERef (r, ty, rk) -> + let rk = match rk with RMut -> "mut " | RShared -> "" in + "&" ^ region_to_string c r ^ " " ^ rk ^ expr_to_string c ty + | EVar v -> opt_var_to_string c v + +and generic_arg_to_string (c : print_config) (g : generic_arg) : string = + match g with + | GExpr e -> expr_to_string c e + | GValue l -> ( + let l = literal_to_string c l in + match c.tgt with + | TkPattern | TkPretty -> l + | TkName -> StringUtils.capitalize_first_letter l) + | GRegion r -> region_to_string c r + +and generic_args_to_string (c : print_config) (generics : generic_args) : string + = + if generics = [] then "" + else + let generics = List.map (generic_arg_to_string c) generics in + match c.tgt with + | TkPattern | TkPretty -> "<" ^ String.concat ", " generics ^ ">" + | TkName -> String.concat "" generics + +(* + * Check if two patterns are convertible, and compute the common "convertible" + * suffix. + *) + +type inj_map = { m0 : var VarMap.t; m1 : var VarMap.t } + +let empty_inj_map = { m0 = VarMap.empty; m1 = VarMap.empty } + +type conv_map = { rmap : inj_map; vmap : inj_map } + +let empty_conv_map = { rmap = empty_inj_map; vmap = empty_inj_map } + +open Result + +let ( let* ) o f = match o with Error e -> Error e | Ok x -> f x + +let gen_var_convertible (m : inj_map) (v0 : var) (v1 : var) : + (inj_map, unit) result = + match (VarMap.find_opt v0 m.m0, VarMap.find_opt v1 m.m1) with + | None, None -> + let m0 = VarMap.add v0 v1 m.m0 in + let m1 = VarMap.add v1 v0 m.m0 in + Ok { m0; m1 } + | Some v1', Some v0' -> if v1 = v1' && v0 = v0' then Ok m else Error () + | _ -> Error () + +let region_convertible (m : conv_map) (r0 : region) (r1 : region) : + (conv_map, unit) result = + match (r0, r1) with + | RStatic, RStatic -> Ok m + | RVar None, RVar None -> Ok m + | RVar (Some r0), RVar (Some r1) -> + let* rmap = gen_var_convertible m.rmap r0 r1 in + Ok { m with rmap } + | _ -> Error () + +let var_convertible (m : conv_map) (v0 : var) (v1 : var) : + (conv_map, unit) result = + let* vmap = gen_var_convertible m.vmap v0 v1 in + Ok { m with vmap } + +let opt_var_convertible (m : conv_map) (v0 : var option) (v1 : var option) : + (conv_map, unit) result = + match (v0, v1) with + | None, None -> Ok m + | Some v0, Some v1 -> var_convertible m v0 v1 + | _ -> Error () + +(** Return the common prefix, and the divergent suffixes. + + The conv map is optional: + - if [Some] it means we are analyzing an Impl pattern elem + - if [None] it means we are not inside an Impl pattern elem + *) +let rec pattern_common_prefix_aux (m : conv_map option) (p0 : pattern) + (p1 : pattern) : pattern * conv_map option * pattern * pattern = + match (p0, p1) with + | [], _ | _, [] -> ([], m, p0, p1) + | e0 :: tp0, e1 :: tp1 -> ( + match pattern_elem_convertible_aux m e0 e1 with + | Error _ -> ([], m, p0, p1) + | Ok m -> + let pre, m, p0, p1 = pattern_common_prefix_aux m tp0 tp1 in + (e0 :: pre, m, p0, p1)) + +(** We use the result type because otherwise we have options of options, which + is confusing. + *) +and pattern_elem_convertible_aux (m : conv_map option) (p0 : pattern_elem) + (p1 : pattern_elem) : (conv_map option, unit) result = + match (p0, p1) with + | PIdent (s0, g0), PIdent (s1, g1) -> + if s0 = s1 then + match m with + | None -> + (* No map: we are not inside an impl block. + We must check that there are no variables in the elements, + that is they are convertible with an empty map *) + let* m = generic_args_convertible_aux empty_conv_map g0 g1 in + if m = empty_conv_map then Ok None else Error () + | Some m -> + (* There is a map: we are inside an impl block *) + let* nm = generic_args_convertible_aux m g0 g1 in + Ok (Some nm) + else Error () + | PImpl e0, PImpl e1 -> + let nm = empty_conv_map in + let* _ = expr_convertible_aux nm e0 e1 in + Ok m + | _ -> Error () + +and expr_convertible_aux (m : conv_map) (e0 : expr) (e1 : expr) : + (conv_map, unit) result = + match (e0, e1) with + | EComp p0, EComp p1 -> + let _, nm, p0, p1 = pattern_common_prefix_aux (Some m) p0 p1 in + if p0 = [] && p1 = [] then Ok (Option.get nm) else Error () + | EPrimAdt (a0, g0), EPrimAdt (a1, g1) -> + if a0 = a1 then generic_args_convertible_aux m g0 g1 else Error () + | ERef (r0, e0, rk0), ERef (r1, e1, rk1) -> + if rk0 = rk1 then + let* m = region_convertible m r0 r1 in + expr_convertible_aux m e0 e1 + else Error () + | EVar v0, EVar v1 -> opt_var_convertible m v0 v1 + | _ -> Error () + +and generic_args_convertible_aux (m : conv_map) (g0 : generic_args) + (g1 : generic_args) : (conv_map, unit) result = + match (g0, g1) with + | [], [] -> Ok m + | x0 :: g0, x1 :: g1 -> + let* m = generic_arg_convertible_aux m x0 x1 in + generic_args_convertible_aux m g0 g1 + | _ -> Error () + +and generic_arg_convertible_aux (m : conv_map) (g0 : generic_arg) + (g1 : generic_arg) : (conv_map, unit) result = + match (g0, g1) with + | GExpr e0, GExpr e1 -> expr_convertible_aux m e0 e1 + | GValue lit0, GValue lit1 -> if lit0 = lit1 then Ok m else Error () + | GRegion r0, GRegion r1 -> region_convertible m r0 r1 + | _ -> Error () + +(** Return the common prefix, and the divergent suffixes. + + The conv map is optional: + - if [Some] it means we are analyzing an Impl pattern elem + - if [None] it means we are not inside an Impl pattern elem + *) +let pattern_common_prefix (p0 : pattern) (p1 : pattern) : + pattern * pattern * pattern = + let pre, _, p0, p1 = pattern_common_prefix_aux None p0 p1 in + (pre, p0, p1) + +(** Check if two pattern elements are convertible between each other *) +let pattern_elem_convertible (p0 : pattern_elem) (p1 : pattern_elem) : bool = + match pattern_elem_convertible_aux None p0 p1 with + | Error _ -> false + | Ok _ -> true + +(* + * Pattern maps - maps from patterns to values. + * + * We do something simple: the maps are implemented as trees in which + * we attempt to share prefixes of patterns (seen as paths). + *) + +module NameMatcherMap = struct + (* TODO: it would be even better if instead of storing a pattern + we stored a pattern_elem. The performance would likely be + better (there would be less backtracking in find_opt) and + the implementation would be simpler. *) + type 'a t = + | Node of 'a option * (pattern * 'a t) list + (** Nodes are branchings. We do not even attempt to order the branches + to minimize the number of comparisons - we could do this in later + updates. + + A node holds a value. + All the children patterns must be non-empty, and their common + prefixes must be pairwise empty. + *) + + let empty : 'a t = Node (None, []) + + let rec replace (np : pattern) (nv : 'a) (m : 'a t) : 'a t * 'a option = + let (Node (node_v, children)) = m in + (* If the path is empty: stop there *) + if np = [] then (Node (Some nv, children), node_v) + (* Otherwise: explore the children *) + else + let children, replaced = replace_in_children np nv children in + (Node (node_v, children), replaced) + + and replace_in_children (np : pattern) (nv : 'a) + (children : (pattern * 'a t) list) : (pattern * 'a t) list * 'a option = + (* The patterns used in the children should have been selected + such that their common prefixes are pairwise empty. + We thus just need to check each pattern: if there is one which + has a non-empty prefix with np, we insert a node there. Otherwise + we insert a new child at the end. + *) + match children with + | [] -> + (* We reached the end without finding a pattern which has a non-empty + prefix with the current children patterns: we simply insert a new child. *) + ([ (np, Node (Some nv, [])) ], None) + | (child_pat, child_tree) :: children_tl -> + (* Check if there is a common prefix *) + let pre, np_end, child_pat_end = pattern_common_prefix np child_pat in + if pre = [] then + (* Empty prefix: continue *) + let children_tl, replaced = replace_in_children np nv children_tl in + ((child_pat, child_tree) :: children_tl, replaced) + else + (* Non-empty prefix: insert here *) + let (nchild, replaced) : (pattern * 'a t) * 'a option = + match child_pat_end with + | [] -> + (* The child path is a prefix of the current path: insert + in the child *) + let child_tree, replaced = replace np_end nv child_tree in + ((child_pat, child_tree), replaced) + | _ -> + (* The child path is not a prefix of the current path: + insert a branching. + Check if the current path is a prefix of the child path *) + if np_end = [] then + (* Prefix *) + ((pre, Node (Some nv, [ (child_pat_end, child_tree) ])), None) + else + (* Not a prefix *) + ( ( pre, + Node + ( None, + [ + (child_pat_end, child_tree); + (np_end, Node (Some nv, [])); + ] ) ), + None ) + in + (nchild :: children_tl, replaced) + + let add (np : pattern) (nv : 'a) (m : 'a t) : 'a t = + let nm, replaced = replace np nv m in + (* Because of the way we currently use patterns, we should never override + a binding: we thuse check it doesn't happen *) + assert (replaced = None); + nm + + let match_name_with_generics_prefix (ctx : ctx) (c : match_config) + (p : pattern) (n : T.name) (g : T.generic_args) : + (T.name * T.generic_args) option = + if List.length p = List.length n then + if match_name_with_generics ctx c p n g then + Some ([], TypesUtils.empty_generic_args) + else None + else if List.length p < List.length n then + let npre, nend = Collections.List.split_at n (List.length p) in + if match_name ctx c p npre then Some (nend, g) else None + else None + + let rec find_with_generics_opt (ctx : ctx) (c : match_config) + (name : Types.name) (g : Types.generic_args) (m : 'a t) : 'a option = + let (Node (node_v, children)) = m in + (* Check if we reached the destination *) + if name = [] then if g = TypesUtils.empty_generic_args then node_v else None + else + (* Explore the children *) + find_with_generics_in_children_opt ctx c name g children + + and find_with_generics_in_children_opt (ctx : ctx) (c : match_config) + (name : Types.name) (g : Types.generic_args) + (children : (pattern * 'a t) list) : 'a option = + match children with + | [] -> None + | (child_pat, child_tree) :: children -> ( + (* Check if the pattern matches a prefix of the name *) + match match_name_with_generics_prefix ctx c child_pat name g with + | None -> + (* No match: continue. + + Note that because the children patterns are all non-empty and + pairwise disjoint, there is no point in exploring the other + children. + *) + find_with_generics_in_children_opt ctx c name g children + | Some (nend, g) -> + (* Dive into the child *) + find_with_generics_opt ctx c nend g child_tree) + + let find_opt (ctx : ctx) (c : match_config) (name : Types.name) (m : 'a t) : + 'a option = + find_with_generics_opt ctx c name TypesUtils.empty_generic_args m + + let mem (ctx : ctx) (c : match_config) (name : Types.name) (m : 'a t) : bool = + find_opt ctx c name m <> None + + let of_list (ls : (pattern * 'a) list) : 'a t = + List.fold_left (fun m (pat, v) -> add pat v m) empty ls + + let rec to_string_aux (current_indent : string) (indent : string) + (v_to_string : 'a -> string) (m : 'a t) : string = + let (Node (opt_v, children)) = m in + let opt_v = + current_indent ^ PrintUtils.option_to_string v_to_string opt_v + in + let children = + String.concat "\n" + (List.map (child_to_string current_indent indent v_to_string) children) + in + opt_v ^ " [\n" ^ children ^ "\n" ^ current_indent ^ "]" + + and child_to_string (current_indent : string) (indent : string) + (v_to_string : 'a -> string) (child_pat, child) : string = + let c : print_config = { tgt = TkPattern } in + current_indent + ^ pattern_to_string c child_pat + ^ " ->\n" + ^ to_string_aux (current_indent ^ indent) indent v_to_string child + + let to_string (v_to_string : 'a -> string) (m : 'a t) : string = + to_string_aux "" " " v_to_string m +end diff --git a/charon-ml/src/Names.ml b/charon-ml/src/Names.ml deleted file mode 100644 index a27db161..00000000 --- a/charon-ml/src/Names.ml +++ /dev/null @@ -1,80 +0,0 @@ -open Identifiers -module Disambiguator = IdGen () - -(** See the comments for [Name] *) -type path_elem = Ident of string | Disambiguator of Disambiguator.id -[@@deriving show, ord] - -(** A name such as: [std::collections::vector] (which would be represented as - [[Ident "std"; Ident "collections"; Ident "vector"]]) - - - A name really is a list of strings. However, we sometimes need to - introduce unique indices to disambiguate. This mostly happens because - of "impl" blocks in Rust: - {[ - impl List { - ... - } - ]} - - A type in Rust can have several "impl" blocks, and those blocks can - contain items with similar names. For this reason, we need to disambiguate - them with unique indices. Rustc calls those "disambiguators". In rustc, this - gives names like this: - - [betree_main::betree::NodeIdCounter{impl#0}::new] - - note that impl blocks can be nested, and macros sometimes generate - weird names (which require disambiguation): - [betree_main::betree_utils::_#1::{impl#0}::deserialize::{impl#0}] - - Finally, the paths used by rustc are a lot more precise and explicit than - those we expose in LLBC: for instance, every identifier belongs to a specific - namespace (value namespace, type namespace, etc.), and is coupled with a - disambiguator. - - On our side, we want to stay high-level and simple: we use string identifiers - as much as possible, insert disambiguators only when necessary (whenever - we find an "impl" block, typically) and check that the disambiguator is useless - in the other situations (i.e., the disambiguator is always equal to 0). - - Moreover, the items are uniquely disambiguated by their (integer) ids - ([TypeDeclId.id], etc.), and when extracting the code we have to deal with - name clashes anyway. Still, we might want to be more precise in the future. - - Also note that the first path element in the name is always the crate name. - *) -type name = path_elem list [@@deriving show, ord] - -let to_name (ls : string list) : name = List.map (fun s -> Ident s) ls - -type module_name = name [@@deriving show, ord] -type type_name = name [@@deriving show, ord] -type fun_name = name [@@deriving show, ord] -type global_name = name [@@deriving show, ord] - -(** Filter the disambiguators equal to 0 in a name *) -let filter_disambiguators_zero (n : name) : name = - let pred (pe : path_elem) : bool = - match pe with Ident _ -> true | Disambiguator d -> d <> Disambiguator.zero - in - List.filter pred n - -(** Filter the disambiguators in a name *) -let filter_disambiguators (n : name) : name = - let pred (pe : path_elem) : bool = - match pe with Ident _ -> true | Disambiguator _ -> false - in - List.filter pred n - -let as_ident (pe : path_elem) : string = - match pe with - | Ident s -> s - | Disambiguator _ -> raise (Failure "Improper variant") - -let path_elem_to_string (pe : path_elem) : string = - match pe with - | Ident s -> s - | Disambiguator d -> "{" ^ Disambiguator.to_string d ^ "}" - -let name_to_string (name : name) : string = - String.concat "::" (List.map path_elem_to_string name) diff --git a/charon-ml/src/PrimitiveValuesUtils.ml b/charon-ml/src/PrimitiveValuesUtils.ml deleted file mode 100644 index 2ca6d690..00000000 --- a/charon-ml/src/PrimitiveValuesUtils.ml +++ /dev/null @@ -1,7 +0,0 @@ -open PrimitiveValues - -let literal_as_scalar (v : literal) : scalar_value = - match v with Scalar v -> v | _ -> raise (Failure "Unexpected") - -let literal_type_is_integer (t : literal_type) : bool = - match t with Integer _ -> true | _ -> false diff --git a/charon-ml/src/PrintExpressions.ml b/charon-ml/src/PrintExpressions.ml index ceb48955..77d9c246 100644 --- a/charon-ml/src/PrintExpressions.ml +++ b/charon-ml/src/PrintExpressions.ml @@ -1,188 +1,184 @@ (** Pretty-printing for expressions *) -module T = Types -module TU = TypesUtils -module E = Expressions -module A = LlbcAst +open Types +open Expressions +open LlbcAst open PrintUtils -module PT = PrintTypes -module PPV = PrintPrimitiveValues - -let var_id_to_string (id : E.VarId.id) : string = "v@" ^ E.VarId.to_string id - -type expr_formatter = { - rvar_to_string : T.RegionVarId.id -> string; - r_to_string : T.RegionId.id -> string; - type_var_id_to_string : T.TypeVarId.id -> string; - type_decl_id_to_string : T.TypeDeclId.id -> string; - const_generic_var_id_to_string : T.ConstGenericVarId.id -> string; - adt_variant_to_string : T.TypeDeclId.id -> T.VariantId.id -> string; - adt_field_to_string : - T.TypeDeclId.id -> T.VariantId.id option -> T.FieldId.id -> string option; - var_id_to_string : E.VarId.id -> string; - adt_field_names : - T.TypeDeclId.id -> T.VariantId.id option -> string list option; - fun_decl_id_to_string : A.FunDeclId.id -> string; - global_decl_id_to_string : A.GlobalDeclId.id -> string; -} - -let expr_to_etype_formatter (fmt : expr_formatter) : PT.etype_formatter = - { - PT.r_to_string = PT.erased_region_to_string; - PT.type_var_id_to_string = fmt.type_var_id_to_string; - PT.type_decl_id_to_string = fmt.type_decl_id_to_string; - PT.const_generic_var_id_to_string = fmt.const_generic_var_id_to_string; - PT.global_decl_id_to_string = fmt.global_decl_id_to_string; - } - -let expr_to_rtype_formatter (fmt : expr_formatter) : PT.rtype_formatter = - { - PT.r_to_string = PT.region_to_string fmt.r_to_string; - PT.type_var_id_to_string = fmt.type_var_id_to_string; - PT.type_decl_id_to_string = fmt.type_decl_id_to_string; - PT.const_generic_var_id_to_string = fmt.const_generic_var_id_to_string; - PT.global_decl_id_to_string = fmt.global_decl_id_to_string; - } - -let expr_to_stype_formatter (fmt : expr_formatter) : PT.stype_formatter = - { - PT.r_to_string = PT.region_to_string fmt.rvar_to_string; - PT.type_var_id_to_string = fmt.type_var_id_to_string; - PT.type_decl_id_to_string = fmt.type_decl_id_to_string; - PT.const_generic_var_id_to_string = fmt.const_generic_var_id_to_string; - PT.global_decl_id_to_string = fmt.global_decl_id_to_string; - } - -let rec projection_to_string (fmt : expr_formatter) (s : string) - (p : E.projection) : string = +open PrintTypes + +let var_id_to_pretty_string (id : var_id) : string = "v@" ^ VarId.to_string id + +let fun_decl_id_to_pretty_string (id : FunDeclId.id) : string = + "FunDecl@" ^ FunDeclId.to_string id + +let fun_decl_id_to_string (env : ('a, 'b) fmt_env) (id : FunDeclId.id) : string + = + match FunDeclId.Map.find_opt id env.fun_decls with + | None -> fun_decl_id_to_pretty_string id + | Some def -> name_to_string env def.name + +let var_to_string (v : var) : string = + match v.name with + | None -> var_id_to_pretty_string v.index + | Some name -> name ^ "^" ^ VarId.to_string v.index + +let var_id_to_string (env : ('a, 'b) fmt_env) (id : VarId.id) : string = + match List.find_opt (fun (i, _) -> i = id) env.locals with + | None -> var_id_to_pretty_string id + | Some (_, name) -> ( + match name with + | None -> var_id_to_pretty_string id + | Some name -> name ^ "^" ^ VarId.to_string id) + +let rec projection_to_string (env : ('a, 'b) fmt_env) (s : string) + (p : projection) : string = match p with | [] -> s | pe :: p' -> let s = match pe with - | E.Deref -> "*(" ^ s ^ ")" - | E.DerefBox -> "deref_box(" ^ s ^ ")" - | E.Field (E.ProjOption variant_id, fid) -> - assert (variant_id = T.option_some_id); - assert (fid = T.FieldId.zero); - "(" ^ s ^ " as Option::Some)." ^ T.FieldId.to_string fid - | E.Field (E.ProjTuple _, fid) -> - "(" ^ s ^ ")." ^ T.FieldId.to_string fid - | E.Field (E.ProjAdt (adt_id, opt_variant_id), fid) -> ( + | Deref -> "*(" ^ s ^ ")" + | DerefBox -> "deref_box(" ^ s ^ ")" + | Field (ProjTuple _, fid) -> "(" ^ s ^ ")." ^ FieldId.to_string fid + | Field (ProjAdt (adt_id, opt_variant_id), fid) -> ( let field_name = - match fmt.adt_field_to_string adt_id opt_variant_id fid with + match adt_field_to_string env adt_id opt_variant_id fid with | Some field_name -> field_name - | None -> T.FieldId.to_string fid + | None -> FieldId.to_string fid in match opt_variant_id with | None -> "(" ^ s ^ ")." ^ field_name | Some variant_id -> let variant_name = - fmt.adt_variant_to_string adt_id variant_id + adt_variant_to_string env adt_id variant_id in "(" ^ s ^ " as " ^ variant_name ^ ")." ^ field_name) in - projection_to_string fmt s p' + projection_to_string env s p' -let place_to_string (fmt : expr_formatter) (p : E.place) : string = - let var = fmt.var_id_to_string p.E.var_id in - projection_to_string fmt var p.E.projection +let place_to_string (env : ('a, 'b) fmt_env) (p : place) : string = + let var = var_id_to_string env p.var_id in + projection_to_string env var p.projection -let unop_to_string (unop : E.unop) : string = - match unop with - | E.Not -> "¬" - | E.Neg -> "-" - | E.Cast (src, tgt) -> - "cast<" - ^ PPV.integer_type_to_string src - ^ "," - ^ PPV.integer_type_to_string tgt +let cast_kind_to_string (_env : ('a, 'b) fmt_env) (cast : cast_kind) : string = + match cast with + | CastInteger (src, tgt) -> + "cast<" ^ integer_type_to_string src ^ "," ^ integer_type_to_string tgt ^ ">" -let binop_to_string (binop : E.binop) : string = +let unop_to_string (env : ('a, 'b) fmt_env) (unop : unop) : string = + match unop with + | Not -> "¬" + | Neg -> "-" + | Cast cast_kind -> cast_kind_to_string env cast_kind + +let binop_to_string (binop : binop) : string = match binop with - | E.BitXor -> "^" - | E.BitAnd -> "&" - | E.BitOr -> "|" - | E.Eq -> "==" - | E.Lt -> "<" - | E.Le -> "<=" - | E.Ne -> "!=" - | E.Ge -> ">=" - | E.Gt -> ">" - | E.Div -> "/" - | E.Rem -> "%" - | E.Add -> "+" - | E.Sub -> "-" - | E.Mul -> "*" - | E.Shl -> "<<" - | E.Shr -> ">>" - -let operand_to_string (fmt : expr_formatter) (op : E.operand) : string = + | BitXor -> "^" + | BitAnd -> "&" + | BitOr -> "|" + | Eq -> "==" + | Lt -> "<" + | Le -> "<=" + | Ne -> "!=" + | Ge -> ">=" + | Gt -> ">" + | Div -> "/" + | Rem -> "%" + | Add -> "+" + | Sub -> "-" + | Mul -> "*" + | Shl -> "<<" + | Shr -> ">>" + +let assumed_fun_id_to_string (aid : assumed_fun_id) (generics : string) : string + = + match aid with + | BoxNew -> "alloc::boxed::Box" ^ generics ^ "::new" + | BoxFree -> "alloc::alloc::box_free" ^ generics + | ArrayIndexShared -> "@ArrayIndexShared" ^ generics + | ArrayIndexMut -> "@ArrayIndexMut" ^ generics + | ArrayToSliceShared -> "@ArrayToSliceShared" ^ generics + | ArrayToSliceMut -> "@ArrayToSliceMut" ^ generics + | ArrayRepeat -> "@ArrayRepeat" ^ generics + | SliceIndexShared -> "@SliceIndexShared" ^ generics + | SliceIndexMut -> "@SliceIndexMut" ^ generics + +let fun_id_or_trait_method_ref_to_string (env : ('a, 'b) fmt_env) + (r : fun_id_or_trait_method_ref) (generics : string) : string = + match r with + | TraitMethod (trait_ref, method_name, _) -> + trait_ref_to_string env trait_ref ^ "::" ^ method_name ^ generics + | FunId (FRegular fid) -> fun_decl_id_to_string env fid ^ generics + | FunId (FAssumed aid) -> assumed_fun_id_to_string aid generics + +let fn_ptr_to_string (env : ('a, 'b) fmt_env) (ptr : fn_ptr) : string = + let generics = generic_args_to_string env ptr.generics in + fun_id_or_trait_method_ref_to_string env ptr.func generics + +let constant_expr_to_string (env : ('a, 'b) fmt_env) (cv : constant_expr) : + string = + match cv.value with + | CLiteral lit -> + "(" ^ literal_to_string lit ^ " : " ^ ty_to_string env cv.ty ^ ")" + | CVar vid -> const_generic_var_id_to_string env vid + | CTraitConst (trait_ref, generics, const_name) -> + let trait_ref = trait_ref_to_string env trait_ref in + let generics = generic_args_to_string env generics in + trait_ref ^ generics ^ const_name + | CFnPtr fn_ptr -> fn_ptr_to_string env fn_ptr + +let operand_to_string (env : ('a, 'b) fmt_env) (op : operand) : string = match op with - | E.Copy p -> "copy " ^ place_to_string fmt p - | E.Move p -> "move " ^ place_to_string fmt p - | E.Constant (ty, cv) -> - "(" ^ PPV.literal_to_string cv ^ " : " - ^ PT.ety_to_string (expr_to_etype_formatter fmt) ty - ^ ")" - -let rvalue_to_string (fmt : expr_formatter) (rv : E.rvalue) : string = + | Copy p -> "copy " ^ place_to_string env p + | Move p -> "move " ^ place_to_string env p + | Constant cv -> constant_expr_to_string env cv + +let rvalue_to_string (env : ('a, 'b) fmt_env) (rv : rvalue) : string = match rv with - | E.Use op -> operand_to_string fmt op - | E.Ref (p, bk) -> ( - let p = place_to_string fmt p in + | Use op -> operand_to_string env op + | RvRef (p, bk) -> ( + let p = place_to_string env p in match bk with - | E.Shared -> "&" ^ p - | E.Mut -> "&mut " ^ p - | E.TwoPhaseMut -> "&two-phase " ^ p - | E.Shallow -> "&shallow " ^ p) - | E.UnaryOp (unop, op) -> unop_to_string unop ^ " " ^ operand_to_string fmt op - | E.BinaryOp (binop, op1, op2) -> - operand_to_string fmt op1 ^ " " ^ binop_to_string binop ^ " " - ^ operand_to_string fmt op2 - | E.Discriminant p -> "discriminant(" ^ place_to_string fmt p ^ ")" - | E.Global gid -> "global " ^ fmt.global_decl_id_to_string gid - | E.Aggregate (akind, ops) -> ( - let ops = List.map (operand_to_string fmt) ops in + | BShared -> "&" ^ p + | BMut -> "&mut " ^ p + | BTwoPhaseMut -> "&two-phase " ^ p + | BShallow -> "&shallow " ^ p) + | UnaryOp (unop, op) -> + unop_to_string env unop ^ " " ^ operand_to_string env op + | BinaryOp (binop, op1, op2) -> + operand_to_string env op1 ^ " " ^ binop_to_string binop ^ " " + ^ operand_to_string env op2 + | Discriminant p -> "discriminant(" ^ place_to_string env p ^ ")" + | Global gid -> "global " ^ global_decl_id_to_string env gid + | Aggregate (akind, ops) -> ( + let ops = List.map (operand_to_string env) ops in match akind with - | E.AggregatedTuple -> "(" ^ String.concat ", " ops ^ ")" - | E.AggregatedOption (variant_id, _ty) -> - if variant_id = T.option_none_id then ( - assert (ops = []); - "@Option::None") - else if variant_id = T.option_some_id then ( - assert (List.length ops = 1); - let op = List.hd ops in - "@Option::Some(" ^ op ^ ")") - else raise (Failure "Unreachable") - | E.AggregatedAdt (def_id, opt_variant_id, _regions, _types, _cgs) -> - let adt_name = fmt.type_decl_id_to_string def_id in - let variant_name = - match opt_variant_id with - | None -> adt_name - | Some variant_id -> - adt_name ^ "::" ^ fmt.adt_variant_to_string def_id variant_id - in - let fields = - match fmt.adt_field_names def_id opt_variant_id with - | None -> "(" ^ String.concat ", " ops ^ ")" - | Some field_names -> - let fields = List.combine field_names ops in - let fields = - List.map - (fun (field, value) -> field ^ " = " ^ value ^ ";") - fields - in - let fields = String.concat " " fields in - "{ " ^ fields ^ " }" - in - variant_name ^ " " ^ fields - | E.AggregatedRange ty -> - let fmt = expr_to_etype_formatter fmt in - "@Range " ^ PT.ety_to_string fmt ty - | E.AggregatedArray (ty, cg) -> - let fmt = expr_to_etype_formatter fmt in - "@Array(" ^ PT.ety_to_string fmt ty ^ ", " - ^ PT.const_generic_to_string fmt cg - ^ ")") + | AggregatedAdt (type_id, opt_variant_id, _generics) -> ( + match type_id with + | TTuple -> "(" ^ String.concat ", " ops ^ ")" + | TAdtId def_id -> + let adt_name = type_decl_id_to_string env def_id in + let variant_name = + match opt_variant_id with + | None -> adt_name + | Some variant_id -> + adt_name ^ "::" + ^ adt_variant_to_string env def_id variant_id + in + let fields = + match adt_field_names env def_id opt_variant_id with + | None -> "(" ^ String.concat ", " ops ^ ")" + | Some field_names -> + let fields = List.combine field_names ops in + let fields = + List.map + (fun (field, value) -> field ^ " = " ^ value ^ ";") + fields + in + let fields = String.concat " " fields in + "{ " ^ fields ^ " }" + in + variant_name ^ " " ^ fields + | TAssumed _ -> raise (Failure "Unreachable")) + | AggregatedArray (_ty, _cg) -> "[" ^ String.concat ", " ops ^ "]") diff --git a/charon-ml/src/PrintGAst.ml b/charon-ml/src/PrintGAst.ml index dc974038..d949bbf9 100644 --- a/charon-ml/src/PrintGAst.ml +++ b/charon-ml/src/PrintGAst.ml @@ -1,293 +1,289 @@ (** Pretty-printing for generic AST (ULLBC and LLBC) *) -module T = Types -module TU = TypesUtils -module E = Expressions -module GA = GAst +open Types +open TypesUtils +open GAst open PrintUtils -module PT = PrintTypes -module PE = PrintExpressions +open PrintTypes +open PrintExpressions -type ast_formatter = PE.expr_formatter - -let ast_to_etype_formatter : ast_formatter -> PT.etype_formatter = - PE.expr_to_etype_formatter - -let ast_to_rtype_formatter : ast_formatter -> PT.rtype_formatter = - PE.expr_to_rtype_formatter - -let ast_to_stype_formatter : ast_formatter -> PT.stype_formatter = - PE.expr_to_stype_formatter - -let gdecls_and_gfun_decl_to_ast_formatter - (type_decls : T.type_decl T.TypeDeclId.Map.t) - (fun_decls : 'body GA.gfun_decl GA.FunDeclId.Map.t) - (global_decls : 'gdecl GA.GlobalDeclId.Map.t) - (get_global_decl_name_as_string : 'gdecl -> string) - (fdef : 'body GA.gfun_decl) : ast_formatter = - let rvar_to_string r = - let rvar = T.RegionVarId.nth fdef.signature.region_params r in - PT.region_var_to_string rvar - in - let r_to_string r = PT.region_id_to_string r in - - let type_var_id_to_string vid = - let var = T.TypeVarId.nth fdef.signature.type_params vid in - PT.type_var_to_string var - in - let const_generic_var_id_to_string vid = - let var = T.ConstGenericVarId.nth fdef.signature.const_generic_params vid in - PT.const_generic_var_to_string var - in - let type_decl_id_to_string def_id = - let def = T.TypeDeclId.Map.find def_id type_decls in - name_to_string def.name - in - let adt_variant_to_string = - PT.type_ctx_to_adt_variant_to_string_fun type_decls - in - let var_id_to_string vid = - let var = E.VarId.nth (Option.get fdef.body).locals vid in - var_to_string var - in - let adt_field_names = PT.type_ctx_to_adt_field_names_fun type_decls in - let adt_field_to_string = PT.type_ctx_to_adt_field_to_string_fun type_decls in - let fun_decl_id_to_string def_id = - let def = GA.FunDeclId.Map.find def_id fun_decls in - fun_name_to_string def.name - in - let global_decl_id_to_string def_id = - let def = GA.GlobalDeclId.Map.find def_id global_decls in - get_global_decl_name_as_string def - in - { - rvar_to_string; - r_to_string; - type_var_id_to_string; - type_decl_id_to_string; - const_generic_var_id_to_string; - adt_variant_to_string; - var_id_to_string; - adt_field_names; - adt_field_to_string; - fun_decl_id_to_string; - global_decl_id_to_string; - } - -let call_to_string (fmt : ast_formatter) (indent : string) (call : GA.call) : +let call_to_string (env : ('a, 'b) fmt_env) (indent : string) (call : call) : string = - let ty_fmt = ast_to_etype_formatter fmt in - let t_params = - if List.length call.GA.type_args > 0 then - "<" - ^ String.concat "," (List.map (PT.ty_to_string ty_fmt) call.GA.type_args) - ^ ">" - else "" - in - let args = List.map (PE.operand_to_string fmt) call.GA.args in + let func = fn_ptr_to_string env call.func in + let args = List.map (operand_to_string env) call.args in let args = "(" ^ String.concat ", " args ^ ")" in - let name_args = - match call.GA.func with - | GA.Regular fid -> fmt.fun_decl_id_to_string fid ^ t_params - | GA.Assumed fid -> ( - match fid with - | GA.Replace -> "core::mem::replace" ^ t_params - | GA.BoxNew -> "alloc::boxed::Box" ^ t_params ^ "::new" - | GA.BoxDeref -> "core::ops::deref::Deref::deref" - | GA.BoxDerefMut -> - "core::ops::deref::DerefMut" ^ t_params ^ "::deref_mut" - | GA.BoxFree -> "alloc::alloc::box_free" ^ t_params - | GA.VecNew -> "alloc::vec::Vec" ^ t_params ^ "::new" - | GA.VecPush -> "alloc::vec::Vec" ^ t_params ^ "::push" - | GA.VecInsert -> "alloc::vec::Vec" ^ t_params ^ "::insert" - | GA.VecLen -> "alloc::vec::Vec" ^ t_params ^ "::len" - | GA.VecIndex -> - "core::ops::index::Index::index" - | GA.VecIndexMut -> - "core::ops::index::IndexMut::index_mut" - | GA.ArrayIndexShared -> "@ArrayIndexShared" ^ t_params - | GA.ArrayIndexMut -> "@ArrayIndexMut" ^ t_params - | GA.ArrayToSliceShared -> "@ArrayToSliceShared" ^ t_params - | GA.ArrayToSliceMut -> "@ArrayToSliceMut" ^ t_params - | GA.ArraySubsliceShared -> "@ArraySubsliceShared" ^ t_params - | GA.ArraySubsliceMut -> "@ArraySubsliceMut" ^ t_params - | GA.SliceLen -> "@SliceLen" ^ t_params - | GA.SliceIndexShared -> "@SliceIndexShared" ^ t_params - | GA.SliceIndexMut -> "@SliceIndexMut" ^ t_params - | GA.SliceSubsliceShared -> "@SliceSubsliceShared" ^ t_params - | GA.SliceSubsliceMut -> "@SliceSubsliceMut" ^ t_params) - in - let dest = PE.place_to_string fmt call.GA.dest in - indent ^ dest ^ " := move " ^ name_args ^ args + let dest = place_to_string env call.dest in + indent ^ dest ^ " := move " ^ func ^ args -let assertion_to_string (fmt : ast_formatter) (indent : string) - (a : GA.assertion) : string = - let cond = PE.operand_to_string fmt a.GA.cond in - if a.GA.expected then indent ^ "assert(" ^ cond ^ ")" +let assertion_to_string (env : ('a, 'b) fmt_env) (indent : string) + (a : assertion) : string = + let cond = operand_to_string env a.cond in + if a.expected then indent ^ "assert(" ^ cond ^ ")" else indent ^ "assert(¬" ^ cond ^ ")" -let gfun_decl_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) - (body_to_string : ast_formatter -> string -> string -> 'body -> string) - (def : 'body GA.gfun_decl) : string = - let sty_fmt = ast_to_stype_formatter fmt in - let sty_to_string = PT.sty_to_string sty_fmt in - let ety_fmt = ast_to_etype_formatter fmt in - let ety_to_string = PT.ety_to_string ety_fmt in - let sg = def.signature in +(** Small helper *) +let fun_sig_with_name_to_string (env : ('a, 'b) fmt_env) (indent : string) + (indent_incr : string) (attribute : string option) (name : string option) + (args : var list option) (sg : fun_sig) : string = + let ty_to_string = ty_to_string env in - (* Function name *) - let name = fun_name_to_string def.GA.name in + (* Unsafe keyword *) + let unsafe = if sg.is_unsafe then "unsafe " else "" in - (* Region/type parameters *) - let regions = sg.region_params in - let types = sg.type_params in + (* Generics and predicates *) + let params, trait_clauses = generic_params_to_strings env sg.generics in + let clauses = + predicates_and_trait_clauses_to_string env indent indent_incr + sg.parent_params_info trait_clauses sg.preds + in let params = - if List.length regions + List.length types = 0 then "" - else - let regions = List.map PT.region_var_to_string regions in - let types = List.map PT.type_var_to_string types in - "<" ^ String.concat "," (List.append regions types) ^ ">" + if params = [] then "" else "<" ^ String.concat ", " params ^ ">" in (* Return type *) let ret_ty = sg.output in - let ret_ty = - if TU.ty_is_unit ret_ty then "" else " -> " ^ sty_to_string ret_ty + let ret_ty = if ty_is_unit ret_ty then "" else " -> " ^ ty_to_string ret_ty in + + (* Arguments *) + let args = + match args with + | None -> + let args = List.map ty_to_string sg.inputs in + String.concat ", " args + | Some args -> + let args = List.combine args sg.inputs in + let args = + List.map + (fun (var, rty) -> var_to_string var ^ " : " ^ ty_to_string rty) + args + in + String.concat ", " args + in + + (* Put everything together *) + let attribute = match attribute with None -> "" | Some attr -> attr ^ " " in + let name = match name with None -> "" | Some name -> " " ^ name in + indent ^ attribute ^ unsafe ^ "fn" ^ name ^ params ^ "(" ^ args ^ ")" ^ ret_ty + ^ clauses + +let fun_sig_to_string (env : ('a, 'b) fmt_env) (indent : string) + (indent_incr : string) (sg : fun_sig) : string = + fun_sig_with_name_to_string env indent indent_incr None None None sg + +let gfun_decl_to_string (env : ('a, 'b) fmt_env) (indent : string) + (indent_incr : string) + (body_to_string : ('a, 'b) fmt_env -> string -> string -> 'body -> string) + (def : 'body gfun_decl) : string = + (* Locally update the environment *) + let env = + { env with generics = def.signature.generics; preds = def.signature.preds } in + let sg = def.signature in + + (* Function name *) + let name = name_to_string env def.name in + (* We print the declaration differently if it is opaque (no body) or transparent * (we have access to a body) *) match def.body with | None -> - (* Arguments *) - let input_tys = sg.inputs in - let args = List.map sty_to_string input_tys in - let args = String.concat ", " args in - - (* Put everything together *) - indent ^ "opaque fn " ^ name ^ params ^ "(" ^ args ^ ")" ^ ret_ty + fun_sig_with_name_to_string env indent indent_incr (Some "opaque") + (Some name) None sg | Some body -> + (* Locally update the environment *) + let locals = List.map (fun v -> (v.index, v.name)) body.locals in + let env = { env with locals } in + (* Arguments *) let inputs = List.tl body.locals in let inputs, _aux_locals = Collections.List.split_at inputs body.arg_count in - let args = List.combine inputs sg.inputs in - let args = - List.map - (fun (var, rty) -> var_to_string var ^ " : " ^ sty_to_string rty) - args - in - let args = String.concat ", " args in (* All the locals (with erased regions) *) let locals = List.map (fun var -> indent ^ indent_incr ^ var_to_string var ^ " : " - ^ ety_to_string var.var_ty ^ ";") + ^ ty_to_string env var.var_ty + ^ ";") body.locals in let locals = String.concat "\n" locals in (* Body *) let body = - body_to_string fmt (indent ^ indent_incr) indent_incr body.body + body_to_string env (indent ^ indent_incr) indent_incr body.body in (* Put everything together *) - indent ^ "fn " ^ name ^ params ^ "(" ^ args ^ ")" ^ ret_ty ^ " {\n" - ^ locals ^ "\n\n" ^ body ^ "\n" ^ indent ^ "}" + fun_sig_with_name_to_string env indent indent_incr None (Some name) + (Some inputs) sg + ^ indent ^ "\n{\n" ^ locals ^ "\n\n" ^ body ^ "\n" ^ indent ^ "}" -(** This function pretty-prints a type definition by using a definition context *) -let ctx_and_type_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (global_context : 'global_decl GA.GlobalDeclId.Map.t) - (get_global_decl_name_as_string : 'gdecl -> string) (decl : T.type_decl) : - string = - let type_decl_id_to_string (id : T.TypeDeclId.id) : string = - let decl = T.TypeDeclId.Map.find id type_context in - name_to_string decl.name - in - let global_decl_id_to_string def_id = - let def = GA.GlobalDeclId.Map.find def_id global_context in - get_global_decl_name_as_string def - in - PT.type_decl_to_string type_decl_id_to_string global_decl_id_to_string decl - -(** Generate an [ast_formatter] by using a declaration context and some additional - parameters *) -let decl_ctx_to_ast_formatter (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : 'body GA.gfun_decl GA.FunDeclId.Map.t) - (global_context : 'global_decl GA.GlobalDeclId.Map.t) - (region_vars : T.region_var list) (type_params : T.type_var list) - (const_generic_params : T.const_generic_var list) (locals : GA.var list) - (get_global_decl_name_as_string : 'gdecl -> string) : ast_formatter = - let rvar_to_string vid = - let var = T.RegionVarId.nth region_vars vid in - PT.region_var_to_string var - in - let r_to_string vid = - (* TODO: we might want something more informative *) - PT.region_id_to_string vid - in - let type_var_id_to_string vid = - let var = T.TypeVarId.nth type_params vid in - PT.type_var_to_string var - in - let const_generic_var_id_to_string vid = - let var = T.ConstGenericVarId.nth const_generic_params vid in - PT.const_generic_var_to_string var - in - let type_decl_id_to_string def_id = - let def = T.TypeDeclId.Map.find def_id type_context in - name_to_string def.name +let trait_decl_to_string (env : ('a, 'b) fmt_env) (indent : string) + (indent_incr : string) (def : trait_decl) : string = + (* Locally update the environment *) + let env = { env with generics = def.generics; preds = def.preds } in + + let ty_to_string = ty_to_string env in + + (* Name *) + let name = name_to_string env def.name in + + (* Generics and predicates *) + let params, trait_clauses = generic_params_to_strings env def.generics in + let clauses = + predicates_and_trait_clauses_to_string env indent indent_incr None + trait_clauses def.preds in - let fun_decl_id_to_string def_id = - let def = GA.FunDeclId.Map.find def_id fun_context in - fun_name_to_string def.name + let params = + if params = [] then "" else "<" ^ String.concat ", " params ^ ">" in - let global_decl_id_to_string def_id = - let def = GA.GlobalDeclId.Map.find def_id global_context in - get_global_decl_name_as_string def + + let indent1 = indent ^ indent_incr in + + let items = + let parent_clauses = + List.map + (fun clause -> + indent1 ^ "parent_clause_" + ^ TraitClauseId.to_string clause.clause_id + ^ " : " + ^ trait_clause_to_string env clause + ^ "\n") + def.parent_clauses + in + let consts = + List.map + (fun (name, (ty, opt_id)) -> + let ty = ty_to_string ty in + let lhs = indent1 ^ "const " ^ name ^ " : " ^ ty in + match opt_id with + | None -> lhs ^ "\n" + | Some id -> lhs ^ " = " ^ global_decl_id_to_string env id ^ "\n") + def.consts + in + let types = + List.map + (fun (name, (clauses, opt_ty)) -> + let clauses = List.map (trait_clause_to_string env) clauses in + let clauses = clauses_to_string indent1 indent_incr 0 clauses in + match opt_ty with + | None -> indent1 ^ "type " ^ name ^ clauses ^ "\n" + | Some ty -> + indent1 ^ "type " ^ name ^ " = " ^ ty_to_string ty ^ clauses + ^ "\n") + def.types + in + let required_methods = + List.map + (fun (name, f) -> + indent1 ^ "fn " ^ name ^ " : " ^ fun_decl_id_to_string env f ^ "\n") + def.required_methods + in + let provided_methods = + List.map + (fun (name, f) -> + match f with + | None -> indent1 ^ "fn " ^ name ^ "\n" + | Some f -> + indent1 ^ "fn " ^ name ^ " : " + ^ fun_decl_id_to_string env f + ^ "\n") + def.provided_methods + in + let items = + List.concat + [ + parent_clauses; + consts; + types; + [ indent1 ^ "// Required methods\n" ]; + required_methods; + [ indent1 ^ "// Provided methods\n" ]; + provided_methods; + ] + in + if items = [] then "" else "\n{\n" ^ String.concat "" items ^ "}" in - let var_id_to_string vid = - let var = E.VarId.nth locals vid in - var_to_string var + + "trait " ^ name ^ params ^ clauses ^ items + +let trait_impl_to_string (env : ('a, 'b) fmt_env) (indent : string) + (indent_incr : string) (def : trait_impl) : string = + (* Locally update the environment *) + let env = { env with generics = def.generics; preds = def.preds } in + + let ty_to_string = ty_to_string env in + + (* Name *) + let name = name_to_string env def.name in + + (* Generics and predicates *) + let params, trait_clauses = generic_params_to_strings env def.generics in + let clauses = + predicates_and_trait_clauses_to_string env indent indent_incr None + trait_clauses def.preds in - let adt_variant_to_string = - PT.type_ctx_to_adt_variant_to_string_fun type_context + let params = + if params = [] then "" else "<" ^ String.concat ", " params ^ ">" in - let adt_field_to_string = - PT.type_ctx_to_adt_field_to_string_fun type_context + + let indent1 = indent ^ indent_incr in + + let items = + (* The parent clauses are given by the trait refs of the implemented trait *) + let parent_clauses = + Collections.List.mapi + (fun i trait_ref -> + indent1 ^ "parent_clause" ^ string_of_int i ^ " = " + ^ trait_ref_to_string env trait_ref + ^ "\n") + def.parent_trait_refs + in + let consts = + List.map + (fun (name, (ty, id)) -> + let ty = ty_to_string ty in + let id = global_decl_id_to_string env id in + indent1 ^ "const " ^ name ^ " : " ^ ty ^ " = " ^ id ^ "\n") + def.consts + in + let types = + List.map + (fun (name, (trait_refs, ty)) -> + let trait_refs = + if trait_refs <> [] then + " where [" + ^ String.concat ", " + (List.map (trait_ref_to_string env) trait_refs) + ^ "]" + else "" + in + indent1 ^ "type " ^ name ^ " = " ^ ty_to_string ty ^ trait_refs ^ "\n") + def.types + in + let env_method (name, f) = + indent1 ^ "fn " ^ name ^ " : " ^ fun_decl_id_to_string env f ^ "\n" + in + let required_methods = List.map env_method def.required_methods in + let provided_methods = List.map env_method def.provided_methods in + let methods = + if required_methods <> [] || provided_methods <> [] then + List.concat + [ + [ indent1 ^ "// Required methods\n" ]; + required_methods; + [ indent1 ^ "// Provided methods\n" ]; + provided_methods; + ] + else [] + in + let items = List.concat [ parent_clauses; consts; types; methods ] in + if items = [] then "" else "\n{\n" ^ String.concat "" items ^ "}" in - let adt_field_names = PT.type_ctx_to_adt_field_names_fun type_context in - { - rvar_to_string; - r_to_string; - type_var_id_to_string; - type_decl_id_to_string; - const_generic_var_id_to_string; - adt_variant_to_string; - adt_field_to_string; - var_id_to_string; - adt_field_names; - fun_decl_id_to_string; - global_decl_id_to_string; - } - -(** Generate an [ast_formatter] by using a declaration context in combination - with the variables local to a function's declaration *) -let decl_ctx_and_fun_decl_to_ast_formatter - (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : 'body GA.gfun_decl GA.FunDeclId.Map.t) - (global_context : 'global_decl GA.GlobalDeclId.Map.t) - (get_global_decl_name_as_string : 'gdecl -> string) - (def : 'body GA.gfun_decl) : ast_formatter = - let region_vars = def.signature.region_params in - let type_params = def.signature.type_params in - let cg_params = def.signature.const_generic_params in - let locals = match def.body with None -> [] | Some body -> body.locals in - decl_ctx_to_ast_formatter type_context fun_context global_context region_vars - type_params cg_params locals get_global_decl_name_as_string + + let impl_trait = trait_decl_ref_to_string env def.impl_trait in + "impl" ^ params ^ " " ^ name ^ params ^ " : " ^ impl_trait ^ clauses ^ items diff --git a/charon-ml/src/PrintLlbcAst.ml b/charon-ml/src/PrintLlbcAst.ml index ca77aace..da0d42d3 100644 --- a/charon-ml/src/PrintLlbcAst.ml +++ b/charon-ml/src/PrintLlbcAst.ml @@ -1,78 +1,68 @@ -module T = Types -module TU = TypesUtils -module E = Expressions -module A = LlbcAst open PrintUtils -module PT = PrintTypes -module PPV = PrintPrimitiveValues -module PE = PrintExpressions +open Types +open TypesUtils +open LlbcAst +open PrintTypes +open PrintValues +open PrintExpressions + +type fmt_env = (statement, FunDeclId.id) PrintUtils.fmt_env (** Pretty-printing for LLBC AST (generic functions) *) module Ast = struct include PrintGAst - let decls_and_fun_decl_to_ast_formatter - (type_decls : T.type_decl T.TypeDeclId.Map.t) - (fun_decls : A.fun_decl A.FunDeclId.Map.t) - (global_decls : A.global_decl A.GlobalDeclId.Map.t) (fdef : A.fun_decl) : - ast_formatter = - gdecls_and_gfun_decl_to_ast_formatter type_decls fun_decls global_decls - (fun decl -> global_name_to_string decl.name) - fdef - - let rec statement_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (st : A.statement) : string = - raw_statement_to_string fmt indent indent_incr st.content - - and raw_statement_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (st : A.raw_statement) : string = + let rec statement_to_string (env : fmt_env) (indent : string) + (indent_incr : string) (st : statement) : string = + raw_statement_to_string env indent indent_incr st.content + + and raw_statement_to_string (env : fmt_env) (indent : string) + (indent_incr : string) (st : raw_statement) : string = match st with - | A.Assign (p, rv) -> - indent ^ PE.place_to_string fmt p ^ " := " ^ PE.rvalue_to_string fmt rv - | A.FakeRead p -> indent ^ "fake_read " ^ PE.place_to_string fmt p - | A.SetDiscriminant (p, variant_id) -> + | Assign (p, rv) -> + indent ^ place_to_string env p ^ " := " ^ rvalue_to_string env rv + | FakeRead p -> indent ^ "fake_read " ^ place_to_string env p + | SetDiscriminant (p, variant_id) -> (* TODO: improve this to lookup the variant name by using the def id *) - indent ^ "set_discriminant(" ^ PE.place_to_string fmt p ^ ", " - ^ T.VariantId.to_string variant_id + indent ^ "set_discriminant(" ^ place_to_string env p ^ ", " + ^ VariantId.to_string variant_id ^ ")" - | A.Drop p -> indent ^ "drop " ^ PE.place_to_string fmt p - | A.Assert a -> assertion_to_string fmt indent a - | A.Call call -> call_to_string fmt indent call - | A.Panic -> indent ^ "panic" - | A.Return -> indent ^ "return" - | A.Break i -> indent ^ "break " ^ string_of_int i - | A.Continue i -> indent ^ "continue " ^ string_of_int i - | A.Nop -> indent ^ "nop" - | A.Sequence (st1, st2) -> - statement_to_string fmt indent indent_incr st1 + | Drop p -> indent ^ "drop " ^ place_to_string env p + | Assert a -> assertion_to_string env indent a + | Call call -> call_to_string env indent call + | Panic -> indent ^ "panic" + | Return -> indent ^ "return" + | Break i -> indent ^ "break " ^ string_of_int i + | Continue i -> indent ^ "continue " ^ string_of_int i + | Nop -> indent ^ "nop" + | Sequence (st1, st2) -> + statement_to_string env indent indent_incr st1 ^ ";\n" - ^ statement_to_string fmt indent indent_incr st2 - | A.Switch switch -> ( + ^ statement_to_string env indent indent_incr st2 + | Switch switch -> ( match switch with - | A.If (op, true_st, false_st) -> - let op = PE.operand_to_string fmt op in + | If (op, true_st, false_st) -> + let op = operand_to_string env op in let inner_indent = indent ^ indent_incr in let inner_to_string = - statement_to_string fmt inner_indent indent_incr + statement_to_string env inner_indent indent_incr in let true_st = inner_to_string true_st in let false_st = inner_to_string false_st in indent ^ "if (" ^ op ^ ") {\n" ^ true_st ^ "\n" ^ indent ^ "}\n" ^ indent ^ "else {\n" ^ false_st ^ "\n" ^ indent ^ "}" - | A.SwitchInt (op, _ty, branches, otherwise) -> - let op = PE.operand_to_string fmt op in + | SwitchInt (op, _ty, branches, otherwise) -> + let op = operand_to_string env op in let indent1 = indent ^ indent_incr in let indent2 = indent1 ^ indent_incr in let inner_to_string2 = - statement_to_string fmt indent2 indent_incr + statement_to_string env indent2 indent_incr in let branches = List.map (fun (svl, be) -> let svl = - List.map - (fun sv -> "| " ^ PPV.scalar_value_to_string sv) - svl + List.map (fun sv -> "| " ^ scalar_value_to_string sv) svl in let svl = String.concat " " svl in indent ^ svl ^ " => {\n" ^ inner_to_string2 be ^ "\n" @@ -85,18 +75,18 @@ module Ast = struct ^ inner_to_string2 otherwise ^ "\n" ^ indent1 ^ "}" in indent ^ "switch (" ^ op ^ ") {\n" ^ branches ^ "\n" ^ indent ^ "}" - | A.Match (p, branches, otherwise) -> - let p = PE.place_to_string fmt p in + | Match (p, branches, otherwise) -> + let p = place_to_string env p in let indent1 = indent ^ indent_incr in let indent2 = indent1 ^ indent_incr in let inner_to_string2 = - statement_to_string fmt indent2 indent_incr + statement_to_string env indent2 indent_incr in let branches = List.map (fun (svl, be) -> let svl = - List.map (fun sv -> "| " ^ T.VariantId.to_string sv) svl + List.map (fun sv -> "| " ^ VariantId.to_string sv) svl in let svl = String.concat " " svl in indent ^ svl ^ " => {\n" ^ inner_to_string2 be ^ "\n" @@ -109,130 +99,94 @@ module Ast = struct ^ inner_to_string2 otherwise ^ "\n" ^ indent1 ^ "}" in indent ^ "match (" ^ p ^ ") {\n" ^ branches ^ "\n" ^ indent ^ "}") - | A.Loop loop_st -> + | Loop loop_st -> indent ^ "loop {\n" - ^ statement_to_string fmt (indent ^ indent_incr) indent_incr loop_st + ^ statement_to_string env (indent ^ indent_incr) indent_incr loop_st ^ "\n" ^ indent ^ "}" - let fun_decl_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (def : A.fun_decl) : string = - gfun_decl_to_string fmt indent indent_incr statement_to_string def + let fun_sig_to_string (env : fmt_env) (indent : string) (indent_incr : string) + (sg : fun_sig) : string = + fun_sig_to_string env indent indent_incr sg - let global_decl_to_string (fmt : ast_formatter) (indent : string) - (_indent_incr : string) (def : A.global_decl) : string = - let ety_fmt = ast_to_etype_formatter fmt in - let ety_to_string = PT.ety_to_string ety_fmt in + let fun_decl_to_string (env : fmt_env) (indent : string) + (indent_incr : string) (def : fun_decl) : string = + gfun_decl_to_string env indent indent_incr statement_to_string def + let global_decl_to_string (env : fmt_env) (indent : string) + (_indent_incr : string) (def : global_decl) : string = + (* No need to locally update the environment *) (* Global name *) - let name = global_name_to_string def.A.name in + let name = name_to_string env def.name in (* Type *) - let ty = ety_to_string def.ty in + let ty = ty_to_string env def.ty in - let body_id = fmt.fun_decl_id_to_string def.body_id in + let body_id = fun_decl_id_to_string env def.body in indent ^ "global " ^ name ^ " : " ^ ty ^ " = " ^ body_id end -module PA = Ast (* local module *) - (** Pretty-printing for ASTs (functions based on a declaration context) *) module Crate = struct - (** Generate an [ast_formatter] by using a declaration context in combination - with the variables local to a function's declaration *) - let decl_ctx_and_fun_decl_to_ast_formatter - (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) (def : A.fun_decl) : - PA.ast_formatter = - PrintGAst.decl_ctx_and_fun_decl_to_ast_formatter type_context fun_context - global_context - (fun decl -> global_name_to_string decl.name) - def - - (** Generate an [ast_formatter] by using a declaration context and a global definition *) - let decl_ctx_and_global_decl_to_ast_formatter - (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : 'body A.gfun_decl A.FunDeclId.Map.t) - (global_context : 'global_decl A.GlobalDeclId.Map.t) - (_decl : A.global_decl) : PA.ast_formatter = - let region_vars = [] in - let type_params = [] in - let cg_params = [] in - let locals = [] in - let get_global_decl_name_as_string decl = - global_name_to_string decl.A.name - in - PrintGAst.decl_ctx_to_ast_formatter type_context fun_context global_context - region_vars type_params cg_params locals get_global_decl_name_as_string - - (** This function pretty-prints a type declaration by using a declaration - context *) - let type_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (global_context : A.global_decl T.GlobalDeclId.Map.t) (decl : T.type_decl) - : string = - let get_global_decl_name_as_string decl = - global_name_to_string decl.A.name - in - PrintGAst.ctx_and_type_decl_to_string type_context global_context - get_global_decl_name_as_string decl - - (** This function pretty-prints a global declaration by using a declaration - context *) - let global_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) - (decl : A.global_decl) : string = - let fmt = - decl_ctx_and_global_decl_to_ast_formatter type_context fun_context - global_context decl - in - PA.global_decl_to_string fmt "" " " decl - - (** This function pretty-prints a function declaration by using a declaration - context *) - let fun_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) (def : A.fun_decl) : - string = - let fmt = - decl_ctx_and_fun_decl_to_ast_formatter type_context fun_context - global_context def - in - PA.fun_decl_to_string fmt "" " " def - - let crate_type_decl_to_string (m : A.crate) (decl : T.type_decl) : string = - type_decl_to_string m.types m.globals decl - - let crate_global_decl_to_string (m : A.crate) (decl : A.global_decl) : string - = - global_decl_to_string m.types m.functions m.globals decl + open Ast + + let crate_to_fmt_env (m : crate) : fmt_env = + { + type_decls = m.type_decls; + fun_decls = m.fun_decls; + global_decls = m.global_decls; + trait_decls = m.trait_decls; + trait_impls = m.trait_impls; + generics = empty_generic_params; + preds = empty_predicates; + locals = []; + } + + let crate_fun_decl_to_string (m : crate) (d : fun_decl) : string = + let env = crate_to_fmt_env m in + fun_decl_to_string env "" " " d + + let crate_to_string (m : crate) : string = + let env = crate_to_fmt_env m in - let crate_fun_decl_to_string (m : A.crate) (decl : A.fun_decl) : string = - fun_decl_to_string m.types m.functions m.globals decl - - let crate_to_string (m : A.crate) : string = (* The types *) let type_decls = List.map - (fun (_, d) -> type_decl_to_string m.types m.globals d) - (T.TypeDeclId.Map.bindings m.A.types) + (fun (_, d) -> type_decl_to_string env d) + (TypeDeclId.Map.bindings m.type_decls) in (* The globals *) let global_decls = List.map - (fun (_, d) -> global_decl_to_string m.types m.functions m.globals d) - (A.GlobalDeclId.Map.bindings m.A.globals) + (fun (_, d) -> global_decl_to_string env "" " " d) + (GlobalDeclId.Map.bindings m.global_decls) in (* The functions *) let fun_decls = List.map - (fun (_, d) -> fun_decl_to_string m.types m.functions m.globals d) - (A.FunDeclId.Map.bindings m.A.functions) + (fun (_, d) -> fun_decl_to_string env "" " " d) + (FunDeclId.Map.bindings m.fun_decls) + in + + (* The trait declarations *) + let trait_decls = + List.map + (fun (_, d) -> trait_decl_to_string env "" " " d) + (TraitDeclId.Map.bindings m.trait_decls) + in + + (* The trait implementations *) + let trait_impls = + List.map + (fun (_, d) -> trait_impl_to_string env "" " " d) + (TraitImplId.Map.bindings m.trait_impls) in (* Put everything together *) - let all_defs = List.concat [ type_decls; global_decls; fun_decls ] in + let all_defs = + List.concat + [ type_decls; global_decls; trait_decls; trait_impls; fun_decls ] + in String.concat "\n\n" all_defs end diff --git a/charon-ml/src/PrintTypes.ml b/charon-ml/src/PrintTypes.ml index 5a735f3b..1b3b48af 100644 --- a/charon-ml/src/PrintTypes.ml +++ b/charon-ml/src/PrintTypes.ml @@ -1,210 +1,400 @@ (** Pretty-printing for types *) -module T = Types -module TU = TypesUtils -module E = Expressions -module A = LlbcAst -module PV = PrimitiveValues +include PrintValues +open Types +open TypesUtils +open GAst open PrintUtils -open PrintPrimitiveValues -let type_var_id_to_string (id : T.TypeVarId.id) : string = - "T@" ^ T.TypeVarId.to_string id +let type_var_to_string (tv : type_var) : string = tv.name +let const_generic_var_to_string (v : const_generic_var) : string = v.name -let type_var_to_string (tv : T.type_var) : string = tv.name +let region_var_to_string (rv : region_var) : string = + match rv.name with Some name -> name | None -> RegionId.to_string rv.index -let const_generic_var_id_to_string (id : T.ConstGenericVarId.id) : string = - "N@" ^ T.ConstGenericVarId.to_string id +let ref_kind_to_string (rk : ref_kind) : string = + match rk with RMut -> "Mut" | RShared -> "Shared" -let const_generic_var_to_string (v : T.const_generic_var) : string = v.name +let assumed_ty_to_string (_ : assumed_ty) : string = "Box" -let region_var_to_string (rv : T.region_var) : string = - match rv.name with - | Some name -> name - | None -> T.RegionVarId.to_string rv.index +let trait_clause_id_to_pretty_string (id : trait_clause_id) : string = + "TraitClause@" ^ TraitClauseId.to_string id -let region_var_id_to_string (rid : T.RegionVarId.id) : string = - "rv@" ^ T.RegionVarId.to_string rid +let region_id_to_pretty_string (id : region_id) : string = + "R@" ^ RegionId.to_string id -let region_id_to_string (rid : T.RegionId.id) : string = - "r@" ^ T.RegionId.to_string rid +let type_var_id_to_pretty_string (id : type_var_id) : string = + "T@" ^ TypeVarId.to_string id -let region_to_string (rid_to_string : 'rid -> string) (r : 'rid T.region) : - string = - match r with Static -> "'static" | Var rid -> rid_to_string rid +let const_generic_var_id_to_pretty_string (id : const_generic_var_id) : string = + "C@" ^ ConstGenericVarId.to_string id + +let type_decl_id_to_pretty_string (id : type_decl_id) : string = + "TypeDecl@" ^ TypeDeclId.to_string id + +let trait_decl_id_to_pretty_string (id : trait_decl_id) : string = + "TraitDecl@" ^ TraitDeclId.to_string id + +let trait_impl_id_to_pretty_string (id : trait_impl_id) : string = + "TraitImpl@" ^ TraitImplId.to_string id + +let variant_id_to_pretty_string (id : variant_id) : string = + "Variant@" ^ VariantId.to_string id + +let field_id_to_pretty_string (id : field_id) : string = + "Field@" ^ FieldId.to_string id -let erased_region_to_string (_ : T.erased_region) : string = "'_" +let region_id_to_string (env : ('a, 'b) fmt_env) (id : region_id) : string = + (* Note that the regions are not necessarily ordered following their indices *) + match + List.find_opt (fun (r : region_var) -> r.index = id) env.generics.regions + with + | None -> region_id_to_pretty_string id + | Some r -> region_var_to_string r -let ref_kind_to_string (rk : T.ref_kind) : string = - match rk with Mut -> "Mut" | Shared -> "Shared" +let type_var_id_to_string (env : ('a, 'b) fmt_env) (id : type_var_id) : string = + (* Note that the types are not necessarily ordered following their indices *) + match + List.find_opt (fun (x : type_var) -> x.index = id) env.generics.types + with + | None -> type_var_id_to_pretty_string id + | Some x -> type_var_to_string x -let assumed_ty_to_string (_ : T.assumed_ty) : string = "Box" +let const_generic_var_id_to_string (env : ('a, 'b) fmt_env) + (id : const_generic_var_id) : string = + (* Note that the const generics are not necessarily ordered following their indices *) + match + List.find_opt + (fun (x : const_generic_var) -> x.index = id) + env.generics.const_generics + with + | None -> const_generic_var_id_to_pretty_string id + | Some x -> const_generic_var_to_string x -type 'r type_formatter = { - r_to_string : 'r -> string; - type_var_id_to_string : T.TypeVarId.id -> string; - type_decl_id_to_string : T.TypeDeclId.id -> string; - const_generic_var_id_to_string : T.ConstGenericVarId.id -> string; - global_decl_id_to_string : T.GlobalDeclId.id -> string; -} +let region_to_string (env : ('a, 'b) fmt_env) (r : region) : string = + match r with + | RStatic -> "'static" + | RErased -> "'_" + | RVar rid -> region_id_to_string env rid -type stype_formatter = T.RegionVarId.id T.region type_formatter -type rtype_formatter = T.RegionId.id T.region type_formatter -type etype_formatter = T.erased_region type_formatter +let trait_clause_id_to_string _ id = trait_clause_id_to_pretty_string id -let type_id_to_string (fmt : 'r type_formatter) (id : T.type_id) : string = +let rec type_id_to_string (env : ('a, 'b) fmt_env) (id : type_id) : string = match id with - | T.AdtId id -> fmt.type_decl_id_to_string id - | T.Tuple -> "" - | T.Assumed aty -> ( + | TAdtId id -> type_decl_id_to_string env id + | TTuple -> "" + | TAssumed aty -> ( match aty with - | Box -> "alloc::boxed::Box" - | Vec -> "alloc::vec::Vec" - | Option -> "core::option::Option" - | Str -> "str" - | Array -> "@Array" - | Slice -> "@Slice" - | Range -> "@Range") - -let const_generic_to_string (fmt : 'r type_formatter) (cg : T.const_generic) : + | TBox -> "alloc::boxed::Box" + | TStr -> "str" + | TArray -> "@Array" + | TSlice -> "@Slice") + +and type_decl_id_to_string env def_id = + let def = TypeDeclId.Map.find def_id env.type_decls in + name_to_string env def.name + +and global_decl_id_to_string env def_id = + let def = GlobalDeclId.Map.find def_id env.global_decls in + name_to_string env def.name + +and trait_decl_id_to_string env id = + let def = TraitDeclId.Map.find id env.trait_decls in + name_to_string env def.name + +and trait_impl_id_to_string env id = + let def = TraitImplId.Map.find id env.trait_impls in + name_to_string env def.name + +and const_generic_to_string (env : ('a, 'b) fmt_env) (cg : const_generic) : string = match cg with - | ConstGenericGlobal id -> fmt.global_decl_id_to_string id - | ConstGenericVar id -> fmt.const_generic_var_id_to_string id - | ConstGenericValue lit -> literal_to_string lit + | CgGlobal id -> global_decl_id_to_string env id + | CgVar id -> const_generic_var_id_to_string env id + | CgValue lit -> literal_to_string lit -let rec ty_to_string (fmt : 'r type_formatter) (ty : 'r T.ty) : string = +and ty_to_string (env : ('a, 'b) fmt_env) (ty : ty) : string = match ty with - | T.Adt (id, regions, tys, cgs) -> - let is_tuple = match id with T.Tuple -> true | _ -> false in - let params = params_to_string fmt is_tuple regions tys cgs in - type_id_to_string fmt id ^ params - | T.TypeVar tv -> fmt.type_var_id_to_string tv - | T.Never -> "!" - | T.Literal lit_ty -> literal_type_to_string lit_ty - | T.Ref (r, rty, ref_kind) -> ( + | TAdt (id, generics) -> + let is_tuple = match id with TTuple -> true | _ -> false in + let params = params_to_string env is_tuple generics in + type_id_to_string env id ^ params + | TVar tv -> type_var_id_to_string env tv + | TNever -> "!" + | TLiteral lit_ty -> literal_type_to_string lit_ty + | TTraitType (trait_ref, generics, type_name) -> + let trait_ref = trait_ref_to_string env trait_ref in + let generics = generic_args_to_string env generics in + trait_ref ^ generics ^ "::" ^ type_name + | TRef (r, rty, ref_kind) -> ( match ref_kind with - | T.Mut -> "&" ^ fmt.r_to_string r ^ " mut (" ^ ty_to_string fmt rty ^ ")" - | T.Shared -> "&" ^ fmt.r_to_string r ^ " (" ^ ty_to_string fmt rty ^ ")") + | RMut -> + "&" ^ region_to_string env r ^ " mut (" ^ ty_to_string env rty ^ ")" + | RShared -> + "&" ^ region_to_string env r ^ " (" ^ ty_to_string env rty ^ ")") + | TRawPtr (rty, ref_kind) -> ( + match ref_kind with + | RMut -> "*mut " ^ ty_to_string env rty + | RShared -> "*const " ^ ty_to_string env rty) + | TArrow (inputs, output) -> + let inputs = + "(" ^ String.concat ", " (List.map (ty_to_string env) inputs) ^ ") -> " + in + inputs ^ ty_to_string env output -and params_to_string (fmt : 'r type_formatter) (is_tuple : bool) - (regions : 'r list) (types : 'r T.ty list) (cgs : T.const_generic list) : - string = - let regions = List.map fmt.r_to_string regions in - let types = List.map (ty_to_string fmt) types in - let cgs = List.map (const_generic_to_string fmt) cgs in +and params_to_string (env : ('a, 'b) fmt_env) (is_tuple : bool) + (generics : generic_args) : string = + if is_tuple then + (* Remark: there shouldn't be any trait ref, but we still print them + because if there are we *want* to see them (for debugging purposes) *) + let params, trait_refs = generic_args_to_strings env generics in + let params = "(" ^ String.concat ", " params ^ ")" in + let trait_refs = + if trait_refs = [] then "" else "[" ^ String.concat ", " trait_refs ^ "]" + in + params ^ trait_refs + else generic_args_to_string env generics + +(** Return two lists: + - one for the regions, types, const generics + - one for the trait refs + *) +and generic_args_to_strings (env : ('a, 'b) fmt_env) (generics : generic_args) : + string list * string list = + let { regions; types; const_generics; trait_refs } = generics in + let regions = List.map (region_to_string env) regions in + let types = List.map (ty_to_string env) types in + let cgs = List.map (const_generic_to_string env) const_generics in let params = List.flatten [ regions; types; cgs ] in - let params_s = String.concat ", " params in - if is_tuple then "(" ^ params_s ^ ")" - else if List.length params > 0 then "<" ^ params_s ^ ">" - else "" + let trait_refs = List.map (trait_ref_to_string env) trait_refs in + (params, trait_refs) + +and generic_args_to_string (env : ('a, 'b) fmt_env) (generics : generic_args) : + string = + let params, trait_refs = generic_args_to_strings env generics in + let params = + if params = [] then "" else "<" ^ String.concat ", " params ^ ">" + in + let trait_refs = + if trait_refs = [] then "" else "[" ^ String.concat ", " trait_refs ^ "]" + in + params ^ trait_refs + +and trait_ref_to_string (env : ('a, 'b) fmt_env) (tr : trait_ref) : string = + let trait_id = trait_instance_id_to_string env tr.trait_id in + let generics = generic_args_to_string env tr.generics in + trait_id ^ generics -let sty_to_string (fmt : stype_formatter) (ty : T.sty) : string = - ty_to_string fmt ty +and trait_decl_ref_to_string (env : ('a, 'b) fmt_env) (tr : trait_decl_ref) : + string = + let trait_id = trait_decl_id_to_string env tr.trait_decl_id in + let generics = generic_args_to_string env tr.decl_generics in + trait_id ^ generics + +and trait_instance_id_to_string (env : ('a, 'b) fmt_env) + (id : trait_instance_id) : string = + match id with + | Self -> "Self" + | TraitImpl id -> trait_impl_id_to_string env id + | BuiltinOrAuto id -> trait_decl_id_to_string env id + | Clause id -> trait_clause_id_to_string env id + | ParentClause (inst_id, _decl_id, clause_id) -> + let inst_id = trait_instance_id_to_string env inst_id in + let clause_id = trait_clause_id_to_string env clause_id in + "parent(" ^ inst_id ^ ")::" ^ clause_id + | ItemClause (inst_id, _decl_id, item_name, clause_id) -> + let inst_id = trait_instance_id_to_string env inst_id in + let clause_id = trait_clause_id_to_string env clause_id in + "(" ^ inst_id ^ ")::" ^ item_name ^ "::[" ^ clause_id ^ "]" + | TraitRef tr -> trait_ref_to_string env tr + | FnPointer ty -> "fn_ptr(" ^ ty_to_string env ty ^ ")" + | UnknownTrait msg -> "UNKNOWN(" ^ msg ^ ")" -let rty_to_string (fmt : rtype_formatter) (ty : T.rty) : string = - ty_to_string fmt ty +and impl_elem_to_string (env : ('a, 'b) fmt_env) (e : impl_elem) : string = + (* Locally replace the generics and the predicates *) + let env = { env with generics = e.generics; preds = e.preds } in + let d = + if e.disambiguator = Disambiguator.zero then "" + else "#" ^ Disambiguator.to_string e.disambiguator + in + let ty = ty_to_string env e.ty in + "{" ^ ty ^ d ^ "}" + +and path_elem_to_string (env : ('a, 'b) fmt_env) (e : path_elem) : string = + match e with + | PeIdent (s, d) -> + let d = + if d = Disambiguator.zero then "" else "#" ^ Disambiguator.to_string d + in + s ^ d + | PeImpl impl -> impl_elem_to_string env impl -let ety_to_string (fmt : etype_formatter) (ty : T.ety) : string = - ty_to_string fmt ty +and name_to_string (env : ('a, 'b) fmt_env) (n : name) : string = + let name = List.map (path_elem_to_string env) n in + String.concat "::" name -let field_to_string fmt (f : T.field) : string = +let trait_clause_to_string (env : ('a, 'b) fmt_env) (clause : trait_clause) : + string = + let clause_id = trait_clause_id_to_string env clause.clause_id in + let trait_id = trait_decl_id_to_string env clause.trait_id in + let generics = generic_args_to_string env clause.clause_generics in + "[" ^ clause_id ^ "]: " ^ trait_id ^ generics + +let generic_params_to_strings (env : ('a, 'b) fmt_env) + (generics : generic_params) : string list * string list = + let { regions; types; const_generics; trait_clauses } = generics in + let regions = List.map region_var_to_string regions in + let types = List.map type_var_to_string types in + let cgs = List.map const_generic_var_to_string const_generics in + let params = List.flatten [ regions; types; cgs ] in + let trait_clauses = List.map (trait_clause_to_string env) trait_clauses in + (params, trait_clauses) + +let field_to_string env (f : field) : string = match f.field_name with - | Some field_name -> field_name ^ " : " ^ ty_to_string fmt f.field_ty - | None -> ty_to_string fmt f.field_ty + | Some field_name -> field_name ^ " : " ^ ty_to_string env f.field_ty + | None -> ty_to_string env f.field_ty -let variant_to_string fmt (v : T.variant) : string = +let variant_to_string env (v : variant) : string = v.variant_name ^ "(" - ^ String.concat ", " (List.map (field_to_string fmt) v.fields) + ^ String.concat ", " (List.map (field_to_string env) v.fields) ^ ")" -let type_decl_to_string (type_decl_id_to_string : T.TypeDeclId.id -> string) - (global_decl_id_to_string : T.GlobalDeclId.id -> string) (def : T.type_decl) - : string = - let regions = def.region_params in - let types = def.type_params in - let cgs = def.const_generic_params in - let rid_to_string rid = - match - List.find_opt (fun (rv : T.region_var) -> rv.T.index = rid) regions - with - | Some rv -> region_var_to_string rv - | None -> raise (Failure "Unreachable") +let trait_type_constraint_to_string (env : ('a, 'b) fmt_env) + (ttc : trait_type_constraint) : string = + let trait_ref = trait_ref_to_string env ttc.trait_ref in + let generics = generic_args_to_string env ttc.generics in + let ty = ty_to_string env ttc.ty in + trait_ref ^ generics ^ " = " ^ ty + +(** Helper to format "where" clauses *) +let clauses_to_string (indent : string) (indent_incr : string) + (num_inherited_clauses : int) (clauses : string list) : string = + if clauses = [] then "" + else + let env_clause s = indent ^ indent_incr ^ s ^ "," in + let clauses = List.map env_clause clauses in + let inherited, local = + Collections.List.split_at clauses num_inherited_clauses + in + let delim1 = + if inherited <> [] then [ indent ^ " // Inherited clauses" ] else [] + in + let delim2 = + if inherited <> [] && local <> [] then [ indent ^ " // Local clauses" ] + else [] + in + let clauses = + List.concat [ [ indent ^ "where" ]; delim1; inherited; delim2; local ] + in + "\n" ^ String.concat "\n" clauses + +(** Helper to format "where" clauses *) +let predicates_and_trait_clauses_to_string (env : ('a, 'b) fmt_env) + (indent : string) (indent_incr : string) (params_info : params_info option) + (trait_clauses : string list) (preds : predicates) : string = + let { regions_outlive; types_outlive; trait_type_constraints } = preds in + let region_to_string = region_to_string env in + let regions_outlive = + List.map + (fun (x, y) -> region_to_string x ^ " : " ^ region_to_string y) + regions_outlive in - let r_to_string = region_to_string rid_to_string in - let type_var_id_to_string id = - match List.find_opt (fun (tv : T.type_var) -> tv.T.index = id) types with - | Some tv -> type_var_to_string tv - | None -> raise (Failure "Unreachable") + let types_outlive = + List.map + (fun (x, y) -> ty_to_string env x ^ " : " ^ region_to_string y) + types_outlive in - let const_generic_var_id_to_string id = - match - List.find_opt (fun (v : T.const_generic_var) -> v.T.index = id) cgs - with - | Some v -> const_generic_var_to_string v - | None -> raise (Failure "Unreachable") + let trait_type_constraints = + List.map (trait_type_constraint_to_string env) trait_type_constraints in - let fmt = - { - r_to_string; - type_var_id_to_string; - type_decl_id_to_string; - const_generic_var_id_to_string; - global_decl_id_to_string; - } + (* Split between the inherited clauses and the local clauses *) + match params_info with + | None -> + let clauses = + List.concat [ regions_outlive; types_outlive; trait_type_constraints ] + in + clauses_to_string indent indent_incr 0 clauses + | Some pi -> + let split_at = Collections.List.split_at in + let trait_clauses = split_at trait_clauses pi.num_trait_clauses in + let regions_outlive = split_at regions_outlive pi.num_regions_outlive in + let types_outlive = split_at types_outlive pi.num_types_outlive in + let ttc = split_at trait_type_constraints pi.num_trait_type_constraints in + let inherited, local = + List.split [ trait_clauses; regions_outlive; types_outlive; ttc ] + in + let inherited = List.concat inherited in + let local = List.concat local in + let num_inherited = List.length inherited in + let clauses = List.append inherited local in + clauses_to_string indent indent_incr num_inherited clauses + +let type_decl_to_string (env : ('a, 'b) fmt_env) (def : type_decl) : string = + (* Locally update the generics and the predicates *) + let env = { env with generics = def.generics; preds = def.preds } in + let params, trait_clauses = generic_params_to_strings env def.generics in + let clauses = + predicates_and_trait_clauses_to_string env "" " " None trait_clauses + def.preds in - let name = name_to_string def.name in + let name = name_to_string env def.name in let params = - let regions = List.map region_var_to_string regions in - let types = List.map type_var_to_string types in - let cgs = List.map const_generic_var_to_string cgs in - let params = List.flatten [ regions; types; cgs ] in - if List.length params > 0 then "<" ^ String.concat ", " params ^ ">" else "" + if params <> [] then "<" ^ String.concat ", " params ^ ">" else "" in match def.kind with - | T.Struct fields -> - if List.length fields > 0 then + | Struct fields -> + if fields <> [] then let fields = String.concat "," - (List.map (fun f -> "\n " ^ field_to_string fmt f) fields) + (List.map (fun f -> "\n " ^ field_to_string env f) fields) in - "struct " ^ name ^ params ^ "{" ^ fields ^ "}" - else "struct " ^ name ^ params ^ "{}" - | T.Enum variants -> + "struct " ^ name ^ params ^ clauses ^ "\n{" ^ fields ^ "\n}" + else "struct " ^ name ^ params ^ clauses ^ "{}" + | Enum variants -> let variants = - List.map (fun v -> "| " ^ variant_to_string fmt v) variants + List.map (fun v -> "| " ^ variant_to_string env v) variants in let variants = String.concat "\n" variants in - "enum " ^ name ^ params ^ " =\n" ^ variants - | T.Opaque -> "opaque type " ^ name ^ params + "enum " ^ name ^ params ^ clauses ^ "\n =\n" ^ variants + | Opaque -> "opaque type " ^ name ^ params ^ clauses -let type_ctx_to_adt_variant_to_string_fun (ctx : T.type_decl T.TypeDeclId.Map.t) - : T.TypeDeclId.id -> T.VariantId.id -> string = - fun def_id variant_id -> - let def = T.TypeDeclId.Map.find def_id ctx in - match def.kind with - | Struct _ | Opaque -> raise (Failure "Unreachable") - | Enum variants -> - let variant = T.VariantId.nth variants variant_id in - name_to_string def.name ^ "::" ^ variant.variant_name - -let type_ctx_to_adt_field_names_fun (ctx : T.type_decl T.TypeDeclId.Map.t) : - T.TypeDeclId.id -> T.VariantId.id option -> string list option = - fun def_id opt_variant_id -> - let def = T.TypeDeclId.Map.find def_id ctx in - let fields = TU.type_decl_get_fields def opt_variant_id in - (* There are two cases: either all the fields have names, or none of them - * has names *) - let has_names = List.exists (fun f -> Option.is_some f.T.field_name) fields in - if has_names then - let fields = List.map (fun f -> Option.get f.T.field_name) fields in - Some fields - else None - -let type_ctx_to_adt_field_to_string_fun (ctx : T.type_decl T.TypeDeclId.Map.t) : - T.TypeDeclId.id -> T.VariantId.id option -> T.FieldId.id -> string option = - fun def_id opt_variant_id field_id -> - let def = T.TypeDeclId.Map.find def_id ctx in - let fields = TU.type_decl_get_fields def opt_variant_id in - let field = T.FieldId.nth fields field_id in - field.T.field_name +let adt_variant_to_string (env : ('a, 'b) fmt_env) (def_id : TypeDeclId.id) + (variant_id : VariantId.id) : string = + match TypeDeclId.Map.find_opt def_id env.type_decls with + | None -> + type_decl_id_to_pretty_string def_id + ^ "::" + ^ variant_id_to_pretty_string variant_id + | Some def -> ( + match def.kind with + | Struct _ | Opaque -> raise (Failure "Unreachable") + | Enum variants -> + let variant = VariantId.nth variants variant_id in + name_to_string env def.name ^ "::" ^ variant.variant_name) + +let adt_field_names (env : ('a, 'b) fmt_env) (def_id : TypeDeclId.id) + (opt_variant_id : VariantId.id option) : string list option = + match TypeDeclId.Map.find_opt def_id env.type_decls with + | None -> None + | Some def -> + let fields = type_decl_get_fields def opt_variant_id in + (* There are two cases: either all the fields have names, or none + of them has names *) + let has_names = + List.exists (fun f -> Option.is_some f.field_name) fields + in + if has_names then + let fields = List.map (fun f -> Option.get f.field_name) fields in + Some fields + else None + +let adt_field_to_string (env : ('a, 'b) fmt_env) (def_id : TypeDeclId.id) + (opt_variant_id : VariantId.id option) (field_id : FieldId.id) : + string option = + match TypeDeclId.Map.find_opt def_id env.type_decls with + | None -> None + | Some def -> + let fields = type_decl_get_fields def opt_variant_id in + let field = FieldId.nth fields field_id in + field.field_name diff --git a/charon-ml/src/PrintUllbcAst.ml b/charon-ml/src/PrintUllbcAst.ml index cbaffe9a..d2e30c97 100644 --- a/charon-ml/src/PrintUllbcAst.ml +++ b/charon-ml/src/PrintUllbcAst.ml @@ -1,220 +1,174 @@ open PrintUtils -module T = Types -module TU = TypesUtils -module E = Expressions -module A = UllbcAst -module PT = PrintTypes -module PPV = PrintPrimitiveValues -module PE = PrintExpressions +open Types +open TypesUtils +open UllbcAst +open PrintTypes +open PrintValues +open PrintExpressions + +type fmt_env = (blocks, global_body option) PrintUtils.fmt_env (** Pretty-printing for ULLBC AST (generic functions) *) module Ast = struct include PrintGAst - let decls_and_fun_decl_to_ast_formatter - (type_decls : T.type_decl T.TypeDeclId.Map.t) - (fun_decls : A.fun_decl A.FunDeclId.Map.t) - (global_decls : A.global_decl A.GlobalDeclId.Map.t) (fdef : A.fun_decl) : - ast_formatter = - gdecls_and_gfun_decl_to_ast_formatter type_decls fun_decls global_decls - (fun decl -> global_name_to_string decl.name) - fdef - - let rec statement_to_string (fmt : ast_formatter) (indent : string) - (st : A.statement) : string = - raw_statement_to_string fmt indent st.content - - and raw_statement_to_string (fmt : ast_formatter) (indent : string) - (st : A.raw_statement) : string = + let rec statement_to_string (env : fmt_env) (indent : string) (st : statement) + : string = + raw_statement_to_string env indent st.content + + and raw_statement_to_string (env : fmt_env) (indent : string) + (st : raw_statement) : string = match st with - | A.Assign (p, rv) -> - indent ^ PE.place_to_string fmt p ^ " := " ^ PE.rvalue_to_string fmt rv - | A.FakeRead p -> indent ^ "fake_read " ^ PE.place_to_string fmt p - | A.SetDiscriminant (p, variant_id) -> - (* TODO: improve this to lookup the variant name by using the def id *) - indent ^ "set_discriminant(" ^ PE.place_to_string fmt p ^ ", " - ^ T.VariantId.to_string variant_id + | Assign (p, rv) -> + indent ^ place_to_string env p ^ " := " ^ rvalue_to_string env rv + | FakeRead p -> indent ^ "fake_read " ^ place_to_string env p + | SetDiscriminant (p, variant_id) -> + (* TODO: improve this to lookup the variant name by using the def id + (we are missing the def id here) *) + indent ^ "set_discriminant(" ^ place_to_string env p ^ ", " + ^ variant_id_to_pretty_string variant_id ^ ")" - | A.StorageDead var_id -> - indent ^ "storage_dead " ^ fmt.var_id_to_string var_id - | A.Deinit p -> indent ^ "deinit " ^ PE.place_to_string fmt p + | StorageDead var_id -> + indent ^ "storage_dead " ^ var_id_to_string env var_id + | Deinit p -> indent ^ "deinit " ^ place_to_string env p - let switch_to_string (indent : string) (tgt : A.switch) : string = + let switch_to_string (indent : string) (tgt : switch) : string = match tgt with - | A.If (b0, b1) -> + | If (b0, b1) -> let b0 = block_id_to_string b0 in let b1 = block_id_to_string b1 in indent ^ "[true -> " ^ b0 ^ "; false -> " ^ b1 ^ "]" - | A.SwitchInt (_int_ty, branches, otherwise) -> + | SwitchInt (_int_ty, branches, otherwise) -> let branches = List.map (fun (sv, bid) -> - PPV.scalar_value_to_string sv - ^ " -> " ^ block_id_to_string bid ^ "; ") + scalar_value_to_string sv ^ " -> " ^ block_id_to_string bid ^ "; ") branches in let branches = String.concat "" branches in let otherwise = "_ -> " ^ block_id_to_string otherwise in indent ^ "[" ^ branches ^ otherwise ^ "]" - let rec terminator_to_string (fmt : ast_formatter) (indent : string) - (st : A.terminator) : string = - raw_terminator_to_string fmt indent st.content + let rec terminator_to_string (env : fmt_env) (indent : string) + (st : terminator) : string = + raw_terminator_to_string env indent st.content - and raw_terminator_to_string (fmt : ast_formatter) (indent : string) - (st : A.raw_terminator) : string = + and raw_terminator_to_string (env : fmt_env) (indent : string) + (st : raw_terminator) : string = match st with - | A.Goto bid -> indent ^ "goto " ^ block_id_to_string bid + | Goto bid -> indent ^ "goto " ^ block_id_to_string bid | Switch (op, tgts) -> - indent ^ "switch " - ^ PE.operand_to_string fmt op + indent ^ "switch " ^ operand_to_string env op ^ switch_to_string indent tgts - | A.Panic -> indent ^ "panic" - | A.Return -> indent ^ "return" - | A.Unreachable -> indent ^ "unreachable" - | A.Drop (p, bid) -> - indent ^ "drop " ^ PE.place_to_string fmt p ^ ";\n" ^ indent ^ "goto " + | Panic -> indent ^ "panic" + | Return -> indent ^ "return" + | Unreachable -> indent ^ "unreachable" + | Drop (p, bid) -> + indent ^ "drop " ^ place_to_string env p ^ ";\n" ^ indent ^ "goto " ^ block_id_to_string bid - | A.Call (call, bid) -> - call_to_string fmt indent call + | Call (call, bid) -> + call_to_string env indent call ^ ";\n" ^ indent ^ "goto " ^ block_id_to_string bid - | A.Assert (a, bid) -> - assertion_to_string fmt indent a + | Assert (a, bid) -> + assertion_to_string env indent a ^ ";\n" ^ indent ^ "goto " ^ block_id_to_string bid - let block_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (id : A.BlockId.id) (block : A.block) : string = + let block_to_string (env : fmt_env) (indent : string) (indent_incr : string) + (id : BlockId.id) (block : block) : string = let indent1 = indent ^ indent_incr in let statements = List.map - (fun st -> statement_to_string fmt indent1 st ^ ";\n") - block.A.statements + (fun st -> statement_to_string env indent1 st ^ ";\n") + block.statements in - let terminator = terminator_to_string fmt indent1 block.A.terminator in + let terminator = terminator_to_string env indent1 block.terminator in indent ^ block_id_to_string id ^ " {\n" ^ String.concat "" statements ^ terminator ^ ";\n" ^ indent ^ "}" - let blocks_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (blocks : A.block list) : string = - let blocks = - A.BlockId.mapi (block_to_string fmt indent indent_incr) blocks - in + let blocks_to_string (env : fmt_env) (indent : string) (indent_incr : string) + (blocks : block list) : string = + let blocks = BlockId.mapi (block_to_string env indent indent_incr) blocks in String.concat "\n\n" blocks - let fun_decl_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (def : A.fun_decl) : string = - gfun_decl_to_string fmt indent indent_incr blocks_to_string def - - let global_decl_to_string (fmt : ast_formatter) (indent : string) - (indent_incr : string) (def : A.global_decl) : string = - let ety_fmt = ast_to_etype_formatter fmt in - let ety_to_string = PT.ety_to_string ety_fmt in + let fun_decl_to_string (env : fmt_env) (indent : string) + (indent_incr : string) (def : fun_decl) : string = + gfun_decl_to_string env indent indent_incr blocks_to_string def - let name = fun_name_to_string def.A.name in - let ty = ety_to_string def.A.ty in + let global_decl_to_string (env : fmt_env) (indent : string) + (indent_incr : string) (def : global_decl) : string = + (* We don't need to update the environment *) + let name = name_to_string env def.name in + let ty = ty_to_string env def.ty in (* We print the declaration differently if it is opaque (no body) or transparent * (we have access to a body) *) - match def.A.body with + match def.body with | None -> (* Put everything together *) indent ^ "opaque global " ^ name ^ " : " ^ ty | Some body -> - let body = blocks_to_string fmt indent indent_incr body.GA.body in + let body = blocks_to_string env indent indent_incr body.body in indent ^ "global " ^ name ^ " : " ^ ty ^ " =\n" ^ body end -module PA = Ast (* local module *) - (** Pretty-printing for ASTs (functions based on a declaration context) *) module Crate = struct - (** Generate an [ast_formatter] by using a declaration context in combination - with the variables local to a function's declaration *) - let decl_ctx_and_fun_decl_to_ast_formatter - (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) (def : A.fun_decl) : - PA.ast_formatter = - PrintGAst.decl_ctx_and_fun_decl_to_ast_formatter type_context fun_context - global_context - (fun decl -> global_name_to_string decl.name) - def - - (** Generate an [ast_formatter] by using a declaration context and a global definition *) - let decl_ctx_and_global_decl_to_ast_formatter - (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : 'body A.gfun_decl A.FunDeclId.Map.t) - (global_context : 'global_decl A.GlobalDeclId.Map.t) - (decl : A.global_decl) : PA.ast_formatter = - let region_vars = [] in - let type_params = [] in - let cg_params = [] in - let locals = match decl.body with None -> [] | Some body -> body.locals in - let get_global_decl_name_as_string decl = - global_name_to_string decl.A.name - in - PrintGAst.decl_ctx_to_ast_formatter type_context fun_context global_context - region_vars type_params cg_params locals get_global_decl_name_as_string - - (** This function pretty-prints a type declaration by using a declaration - context *) - let type_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) (decl : T.type_decl) - : string = - let get_global_decl_name_as_string decl = - global_name_to_string decl.A.name + open Ast + + let crate_to_string (m : crate) : string = + let env : fmt_env = + { + type_decls = m.type_decls; + fun_decls = m.fun_decls; + global_decls = m.global_decls; + trait_decls = m.trait_decls; + trait_impls = m.trait_impls; + generics = empty_generic_params; + preds = empty_predicates; + locals = []; + } in - PrintGAst.ctx_and_type_decl_to_string type_context global_context - get_global_decl_name_as_string decl - - (** This function pretty-prints a global declaration by using a declaration - context *) - let global_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) - (decl : A.global_decl) : string = - let fmt = - decl_ctx_and_global_decl_to_ast_formatter type_context fun_context - global_context decl - in - PA.global_decl_to_string fmt "" " " decl - - (** This function pretty-prints a function declaration by using a declaration - context *) - let fun_decl_to_string (type_context : T.type_decl T.TypeDeclId.Map.t) - (fun_context : A.fun_decl A.FunDeclId.Map.t) - (global_context : A.global_decl A.GlobalDeclId.Map.t) (def : A.fun_decl) : - string = - let fmt = - decl_ctx_and_fun_decl_to_ast_formatter type_context fun_context - global_context def - in - PA.fun_decl_to_string fmt "" " " def - let crate_to_string (m : A.crate) : string = (* The types *) let type_decls = List.map - (fun (_, d) -> type_decl_to_string m.types m.globals d) - (T.TypeDeclId.Map.bindings m.A.types) + (fun (_, d) -> type_decl_to_string env d) + (TypeDeclId.Map.bindings m.type_decls) in (* The globals *) let global_decls = List.map - (fun (_, d) -> global_decl_to_string m.types m.functions m.globals d) - (A.GlobalDeclId.Map.bindings m.A.globals) + (fun (_, d) -> global_decl_to_string env "" " " d) + (GlobalDeclId.Map.bindings m.global_decls) in (* The functions *) let fun_decls = List.map - (fun (_, d) -> fun_decl_to_string m.types m.functions m.globals d) - (A.FunDeclId.Map.bindings m.A.functions) + (fun (_, d) -> fun_decl_to_string env "" " " d) + (FunDeclId.Map.bindings m.fun_decls) + in + + (* The trait declarations *) + let trait_decls = + List.map + (fun (_, d) -> trait_decl_to_string env "" " " d) + (TraitDeclId.Map.bindings m.trait_decls) + in + + (* The trait implementations *) + let trait_impls = + List.map + (fun (_, d) -> trait_impl_to_string env "" " " d) + (TraitImplId.Map.bindings m.trait_impls) in (* Put everything together *) - let all_defs = List.concat [ type_decls; global_decls; fun_decls ] in + let all_defs = + List.concat + [ type_decls; global_decls; trait_decls; trait_impls; fun_decls ] + in String.concat "\n\n" all_defs end diff --git a/charon-ml/src/PrintUtils.ml b/charon-ml/src/PrintUtils.ml index 0d9dc60b..b2fdad55 100644 --- a/charon-ml/src/PrintUtils.ml +++ b/charon-ml/src/PrintUtils.ml @@ -1,22 +1,28 @@ -open Names -module T = Types -module TU = TypesUtils -module E = Expressions -module GA = GAst +open Types +open Expressions +open GAst let option_to_string (to_string : 'a -> string) (x : 'a option) : string = match x with Some x -> "Some (" ^ to_string x ^ ")" | None -> "None" -let name_to_string (name : name) : string = Names.name_to_string name -let fun_name_to_string (name : fun_name) : string = name_to_string name -let global_name_to_string (name : global_name) : string = name_to_string name - let block_id_to_string (id : UllbcAst.BlockId.id) : string = "block@" ^ UllbcAst.BlockId.to_string id -let var_id_to_string (id : E.VarId.id) : string = "var@" ^ E.VarId.to_string id - -let var_to_string (v : GA.var) : string = - match v.name with - | None -> var_id_to_string v.index - | Some name -> name ^ "^" ^ E.VarId.to_string v.index +(** The formatting environment can be incomplete: if some information is missing + (for instance we can't find the type variable for a given index) we print + the id in raw format. *) +type ('fun_body, 'global_body) fmt_env = { + type_decls : type_decl TypeDeclId.Map.t; + fun_decls : 'fun_body gfun_decl FunDeclId.Map.t; + global_decls : 'global_body gglobal_decl GlobalDeclId.Map.t; + trait_decls : trait_decl TraitDeclId.Map.t; + trait_impls : trait_impl TraitImplId.Map.t; + generics : generic_params; + (** The variables in the generics don't need to be ordered following their + indices, i.e., the region var of index 0 doesn't have to be at index 0, + etc. We lookup the variables by checking their id, not their position. + *) + preds : predicates; + locals : (VarId.id * string option) list; + (** The local variables don't need to be ordered (same as the generics) *) +} diff --git a/charon-ml/src/PrintPrimitiveValues.ml b/charon-ml/src/PrintValues.ml similarity index 68% rename from charon-ml/src/PrintPrimitiveValues.ml rename to charon-ml/src/PrintValues.ml index d78affaf..44214d52 100644 --- a/charon-ml/src/PrintPrimitiveValues.ml +++ b/charon-ml/src/PrintValues.ml @@ -1,10 +1,7 @@ (** Pretty-printing for primitive values *) -module T = Types -module TU = TypesUtils -module E = Expressions -module A = LlbcAst -open PrimitiveValues +open Values +open Types let integer_type_to_string = function | Isize -> "isize" @@ -22,9 +19,9 @@ let integer_type_to_string = function let literal_type_to_string (ty : literal_type) : string = match ty with - | Integer ity -> integer_type_to_string ity - | Bool -> "bool" - | Char -> "char" + | TInteger ity -> integer_type_to_string ity + | TBool -> "bool" + | TChar -> "char" let big_int_to_string (bi : big_int) : string = Z.to_string bi @@ -33,6 +30,6 @@ let scalar_value_to_string (sv : scalar_value) : string = let literal_to_string (lit : literal) : string = match lit with - | Scalar sv -> scalar_value_to_string sv - | Bool b -> Bool.to_string b - | Char c -> String.make 1 c + | VScalar sv -> scalar_value_to_string sv + | VBool b -> Bool.to_string b + | VChar c -> String.make 1 c diff --git a/charon-ml/src/Scalars.ml b/charon-ml/src/Scalars.ml index 279ece00..d9d39fc9 100644 --- a/charon-ml/src/Scalars.ml +++ b/charon-ml/src/Scalars.ml @@ -1,4 +1,4 @@ -open PrimitiveValues +open Values (** The minimum/maximum values an integer type can have depending on its type *) diff --git a/charon-ml/src/StringUtils.ml b/charon-ml/src/StringUtils.ml new file mode 100644 index 00000000..dbb5963a --- /dev/null +++ b/charon-ml/src/StringUtils.ml @@ -0,0 +1,111 @@ +(** Utilities to work on strings, character per character. + + They operate on ASCII strings, and are used by the project to convert + Rust names: Rust names are not fancy, so it shouldn't be a problem. + + TODO: isn't there a library with good support for strings, somewhere? + *) + +let code_0 = 48 +let code_9 = 57 +let code_A = 65 +let code_Z = 90 +let code_a = 97 +let code_z = 122 + +let is_lowercase_ascii (c : char) : bool = + let c = Char.code c in + code_a <= c && c <= code_z + +let is_uppercase_ascii (c : char) : bool = + let c = Char.code c in + code_A <= c && c <= code_Z + +let is_letter_ascii (c : char) : bool = + is_lowercase_ascii c || is_uppercase_ascii c + +let is_digit_ascii (c : char) : bool = + let c = Char.code c in + code_0 <= c && c <= code_9 + +let lowercase_ascii = Char.lowercase_ascii +let uppercase_ascii = Char.uppercase_ascii + +(** Using buffers as per: + {{: https://stackoverflow.com/questions/29957418/how-to-convert-char-list-to-string-in-ocaml} stackoverflow} + *) +let string_of_chars (chars : char list) : string = + let buf = Buffer.create (List.length chars) in + List.iter (Buffer.add_char buf) chars; + Buffer.contents buf + +let string_to_chars (s : string) : char list = + let length = String.length s in + let rec apply i = + if i = length then [] else String.get s i :: apply (i + 1) + in + apply 0 + +(** This operates on ASCII *) +let to_camel_case (s : string) : string = + (* Note that we rebuild the string in reverse order *) + let apply ((prev_is_under, acc) : bool * char list) (c : char) : + bool * char list = + if c = '_' then (true, acc) + else + let c = if prev_is_under then uppercase_ascii c else c in + (false, c :: acc) + in + let _, chars = List.fold_left apply (true, []) (string_to_chars s) in + string_of_chars (List.rev chars) + +(** This operates on ASCII *) +let to_snake_case (s : string) : string = + (* Note that we rebuild the string in reverse order *) + let apply ((prev_is_low, prev_is_digit, acc) : bool * bool * char list) + (c : char) : bool * bool * char list = + let acc = + if c = '_' then acc + else if prev_is_digit then if is_letter_ascii c then '_' :: acc else acc + else if prev_is_low then + if (is_lowercase_ascii c || is_digit_ascii c) && c <> '_' then acc + else '_' :: acc + else acc + in + let prev_is_low = is_lowercase_ascii c in + let prev_is_digit = is_digit_ascii c in + let c = lowercase_ascii c in + (prev_is_low, prev_is_digit, c :: acc) + in + let _, _, chars = + List.fold_left apply (false, false, []) (string_to_chars s) + in + string_of_chars (List.rev chars) + +(** Applies a map operation. + + This is very inefficient, but shouldn't be used much. + *) +let map (f : char -> string) (s : string) : string = + let sl = List.map f (string_to_chars s) in + let sl = List.map string_to_chars sl in + string_of_chars (List.concat sl) + +let capitalize_first_letter (s : string) : string = + let s = string_to_chars s in + let s = match s with [] -> s | c :: s' -> uppercase_ascii c :: s' in + string_of_chars s + +let lowercase_first_letter (s : string) : string = + let s = string_to_chars s in + let s = match s with [] -> s | c :: s' -> lowercase_ascii c :: s' in + string_of_chars s + +(** Unit tests *) +let _ = + assert (to_camel_case "hello_world" = "HelloWorld"); + assert (to_snake_case "HelloWorld36Hello" = "hello_world36_hello"); + assert (to_snake_case "HELLO" = "hello"); + assert (to_snake_case "T1" = "t1"); + assert (to_camel_case "list" = "List"); + assert (to_snake_case "is_cons" = "is_cons") diff --git a/charon-ml/src/Types.ml b/charon-ml/src/Types.ml index f389b866..b5e74983 100644 --- a/charon-ml/src/Types.ml +++ b/charon-ml/src/Types.ml @@ -1,16 +1,21 @@ open Identifiers -open Names open Meta -open PrimitiveValues +open Values module TypeVarId = IdGen () module TypeDeclId = IdGen () module VariantId = IdGen () module FieldId = IdGen () module GlobalDeclId = IdGen () module ConstGenericVarId = IdGen () +module TraitDeclId = IdGen () +module TraitImplId = IdGen () +module TraitClauseId = IdGen () +module RegionId = IdGen () +module RegionGroupId = IdGen () +module Disambiguator = IdGen () (** We define this type to control the name of the visitor functions - (see e.g., {!Types.iter_ty_base} and {!Types.TypeVar}). + (see e.g., {!class:Types.iter_ty_base} and {!Types.TVar}). *) type type_var_id = TypeVarId.id [@@deriving show, ord] @@ -20,7 +25,7 @@ type const_generic_var_id = ConstGenericVarId.id [@@deriving show, ord] (** Same remark as for {!type_var_id} *) type global_decl_id = GlobalDeclId.id [@@deriving show, ord] -type integer_type = PrimitiveValues.integer_type [@@deriving show, ord] +type integer_type = Values.integer_type [@@deriving show, ord] (** Same remark as for {!type_var_id} *) type variant_id = VariantId.id [@@deriving show, ord] @@ -31,23 +36,33 @@ type field_id = FieldId.id [@@deriving show, ord] (** Same remark as for {!type_var_id} *) type type_decl_id = TypeDeclId.id [@@deriving show, ord] -(** Region variable ids. Used in function signatures. *) -module RegionVarId = IdGen () +(** Same remark as for {!type_var_id} *) +type trait_decl_id = TraitDeclId.id [@@deriving show, ord] -(** Region ids. Used for symbolic executions. *) -module RegionId = IdGen () +(** Same remark as for {!type_var_id} *) +type trait_impl_id = TraitImplId.id [@@deriving show, ord] -module RegionGroupId = IdGen () +(** Same remark as for {!type_var_id} *) +type trait_clause_id = TraitClauseId.id [@@deriving show, ord] + +(** Same remark as for {!type_var_id} *) +type region_id = RegionId.id [@@deriving show, ord] + +(** Same remark as for {!type_var_id} *) +type region_group_id = RegionGroupId.id [@@deriving show, ord] type ('id, 'name) indexed_var = { index : 'id; (** Unique index identifying the variable *) name : 'name; (** Variable name *) } -[@@deriving show] +[@@deriving show, ord] -type type_var = (TypeVarId.id, string) indexed_var [@@deriving show] -type region_var = (RegionVarId.id, string option) indexed_var [@@deriving show] -type literal_type = PrimitiveValues.literal_type [@@deriving show, ord] +type type_var = (TypeVarId.id, string) indexed_var [@@deriving show, ord] + +type region_var = (RegionId.id, string option) indexed_var +[@@deriving show, ord] + +type literal_type = Values.literal_type [@@deriving show, ord] type const_generic_var = { index : ConstGenericVarId.id; @@ -56,54 +71,11 @@ type const_generic_var = { } [@@deriving show, ord] -(** A region. - - Regions are used in function signatures (in which case we use region variable - ids) and in symbolic variables and projections (in which case we use region - ids). - *) -type 'rid region = - | Static (** Static region *) - | Var of 'rid (** Non-static region *) -[@@deriving show, ord] - -(** The type of erased regions. - - We could use unit, but having a dedicated type makes things more explicit. - *) -type erased_region = Erased [@@deriving show, ord] - -(** A group of regions. - - Results from a lifetime analysis: we group the regions with the same - lifetime together, and compute the hierarchy between the regions. - This is necessary to introduce the proper abstraction with the - proper constraints, when evaluating a function call in symbolic mode. -*) -type ('id, 'r) g_region_group = { - id : 'id; - regions : 'r list; - parents : 'id list; -} -[@@deriving show] - -type ('r, 'id) g_region_groups = ('r, 'id) g_region_group list [@@deriving show] - -type region_var_group = (RegionGroupId.id, RegionVarId.id) g_region_group -[@@deriving show] - -type region_var_groups = (RegionGroupId.id, RegionVarId.id) g_region_groups -[@@deriving show] - let all_signed_int_types = [ Isize; I8; I16; I32; I64; I128 ] let all_unsigned_int_types = [ Usize; U8; U16; U32; U64; U128 ] let all_int_types = List.append all_signed_int_types all_unsigned_int_types -type ref_kind = Mut | Shared [@@deriving show, ord] - -(* TODO: Str should be a literal *) -type assumed_ty = Box | Vec | Option | Array | Slice | Str | Range -[@@deriving show, ord] +type ref_kind = RMut | RShared [@@deriving show, ord] (** The variant id for [Option::None] *) let option_none_id = VariantId.of_int 0 @@ -111,30 +83,24 @@ let option_none_id = VariantId.of_int 0 (** The variant id for [Option::Some] *) let option_some_id = VariantId.of_int 1 -(** Type identifier for ADTs. - - ADTs are very general in our encoding: they account for "regular" ADTs, - tuples and also assumed types. -*) -type type_id = AdtId of TypeDeclId.id | Tuple | Assumed of assumed_ty -[@@deriving show, ord] - (** Ancestor for iter visitor for {!Types.const_generic} *) class ['self] iter_const_generic_base = object (_self : 'self) - inherit [_] VisitorsRuntime.iter + inherit [_] iter_literal + method visit_type_decl_id : 'env -> type_decl_id -> unit = fun _ _ -> () method visit_global_decl_id : 'env -> global_decl_id -> unit = fun _ _ -> () method visit_const_generic_var_id : 'env -> const_generic_var_id -> unit = fun _ _ -> () - - method visit_literal : 'env -> literal -> unit = fun _ _ -> () end (** Ancestor for map visitor for {!Types.const_generic} *) class ['self] map_const_generic_base = object (_self : 'self) - inherit [_] VisitorsRuntime.map + inherit [_] map_literal + + method visit_type_decl_id : 'env -> type_decl_id -> type_decl_id = + fun _ x -> x method visit_global_decl_id : 'env -> global_decl_id -> global_decl_id = fun _ x -> x @@ -142,28 +108,30 @@ class ['self] map_const_generic_base = method visit_const_generic_var_id : 'env -> const_generic_var_id -> const_generic_var_id = fun _ x -> x - - method visit_literal : 'env -> literal -> literal = fun _ x -> x end (** Ancestor for reduce visitor for {!Types.const_generic} *) class virtual ['self] reduce_const_generic_base = object (self : 'self) - inherit [_] VisitorsRuntime.reduce + inherit [_] reduce_literal + + method visit_type_decl_id : 'env -> type_decl_id -> 'a = + fun _ _ -> self#zero method visit_global_decl_id : 'env -> global_decl_id -> 'a = fun _ _ -> self#zero method visit_const_generic_var_id : 'env -> const_generic_var_id -> 'a = fun _ _ -> self#zero - - method visit_literal : 'env -> literal -> 'a = fun _ _ -> self#zero end (** Ancestor for mapreduce visitor for {!Types.const_generic} *) class virtual ['self] mapreduce_const_generic_base = object (self : 'self) - inherit [_] VisitorsRuntime.mapreduce + inherit [_] mapreduce_literal + + method visit_type_decl_id : 'env -> type_decl_id -> type_decl_id * 'a = + fun _ x -> (x, self#zero) method visit_global_decl_id : 'env -> global_decl_id -> global_decl_id * 'a = @@ -172,17 +140,14 @@ class virtual ['self] mapreduce_const_generic_base = method visit_const_generic_var_id : 'env -> const_generic_var_id -> const_generic_var_id * 'a = fun _ x -> (x, self#zero) - - method visit_literal : 'env -> literal -> literal * 'a = - fun _ x -> (x, self#zero) end (** Remark: we have to use long names because otherwise we have collisions in - the functions derived for the visitors *) + the functions derived for the visitors. *) type const_generic = - | ConstGenericGlobal of global_decl_id - | ConstGenericVar of const_generic_var_id - | ConstGenericValue of literal + | CgGlobal of global_decl_id + | CgVar of const_generic_var_id + | CgValue of literal [@@deriving show, ord, @@ -221,40 +186,130 @@ type const_generic = polymorphic = false; }] +type trait_item_name = string [@@deriving show, ord] + (** Ancestor for iter visitor for {!type: Types.ty} *) class ['self] iter_ty_base = object (_self : 'self) inherit [_] iter_const_generic - method visit_'r : 'env -> 'r -> unit = fun _ _ -> () + method visit_region_id : 'env -> region_id -> unit = fun _ _ -> () method visit_type_var_id : 'env -> type_var_id -> unit = fun _ _ -> () - method visit_type_id : 'env -> type_id -> unit = fun _ _ -> () method visit_ref_kind : 'env -> ref_kind -> unit = fun _ _ -> () - method visit_literal_type : 'env -> literal_type -> unit = fun _ _ -> () + + method visit_trait_item_name : 'env -> trait_item_name -> unit = + fun _ _ -> () + + method visit_trait_decl_id : 'env -> trait_decl_id -> unit = fun _ _ -> () + method visit_trait_impl_id : 'env -> trait_impl_id -> unit = fun _ _ -> () + + method visit_trait_clause_id : 'env -> trait_clause_id -> unit = + fun _ _ -> () end (** Ancestor for map visitor for {!type: Types.ty} *) class virtual ['self] map_ty_base = object (_self : 'self) inherit [_] map_const_generic - method virtual visit_'r : 'env -> 'r -> 's + method visit_region_id : 'env -> region_id -> region_id = fun _ id -> id method visit_type_var_id : 'env -> type_var_id -> type_var_id = fun _ id -> id - method visit_type_id : 'env -> type_id -> type_id = fun _ id -> id method visit_ref_kind : 'env -> ref_kind -> ref_kind = fun _ rk -> rk - method visit_literal_type : 'env -> literal_type -> literal_type = + method visit_trait_item_name : 'env -> trait_item_name -> trait_item_name = + fun _ x -> x + + method visit_trait_decl_id : 'env -> trait_decl_id -> trait_decl_id = + fun _ x -> x + + method visit_trait_impl_id : 'env -> trait_impl_id -> trait_impl_id = + fun _ x -> x + + method visit_trait_clause_id : 'env -> trait_clause_id -> trait_clause_id = fun _ x -> x end -type 'r ty = - | Adt of type_id * 'r list * 'r ty list * const_generic list - (** {!Types.ty.Adt} encodes ADTs, tuples and assumed types *) - | TypeVar of type_var_id - | Literal of literal_type - | Never - | Ref of 'r * 'r ty * ref_kind +(* TODO: Str should be a literal *) +type assumed_ty = TBox | TArray | TSlice | TStr + +(** Type identifier for ADTs. + + ADTs are very general in our encoding: they account for "regular" ADTs, + tuples and also assumed types. +*) +and type_id = TAdtId of type_decl_id | TTuple | TAssumed of assumed_ty + +and ty = + | TAdt of type_id * generic_args + (** {!Types.ty.TAdt} encodes ADTs, tuples and assumed types *) + | TVar of type_var_id + | TLiteral of literal_type + | TNever + | TRef of region * ty * ref_kind + | TRawPtr of ty * ref_kind + | TTraitType of trait_ref * generic_args * string + (** The string is for the name of the associated type *) + | TArrow of ty list * ty + +and trait_ref = { + trait_id : trait_instance_id; + generics : generic_args; + trait_decl_ref : trait_decl_ref; +} + +and trait_decl_ref = { + trait_decl_id : trait_decl_id; + decl_generics : generic_args; (* The name: annoying field collisions... *) +} + +and generic_args = { + regions : region list; + types : ty list; + const_generics : const_generic list; + trait_refs : trait_ref list; +} + +(** Identifier of a trait instance. *) +and trait_instance_id = + | Self + (** Reference to *self*, in case of trait declarations/implementations *) + | TraitImpl of trait_impl_id (** A specific implementation *) + | BuiltinOrAuto of trait_decl_id + | Clause of trait_clause_id + | ParentClause of trait_instance_id * trait_decl_id * trait_clause_id + | ItemClause of + trait_instance_id * trait_decl_id * trait_item_name * trait_clause_id + | TraitRef of trait_ref + (** Not present in the Rust version of Charon. + + We need this case for instantiations: when calling a function which has + trait clauses, for instance, we substitute the clauses refernced in the + [Clause] and [Self] case with trait references. + + Remark: something potentially confusing is that [trait_clause_id] is used for + different purposes. In the [Clause] case, a trait clause id identifies a local + trait clause (which can thus be substituted). In the other cases, it references + a sub-clause relative to a trait instance id. + *) + | FnPointer of ty + | UnknownTrait of string + (** Not present in the Rust version of Charon. + + We use this in the substitutions, to substitute [Self] when [Self] shouldn't + appear: this allows us to track errors by making sure [Self] indeed did not + appear. + *) + +(** A region. + + This definition doesn't need to be mutually recursive with the others, but + this allows us to factor out the visitors. + *) +and region = + | RStatic (** Static region *) + | RVar of region_id (** Non-static region *) + | RErased (** Erased region *) [@@deriving show, ord, @@ -276,31 +331,165 @@ type 'r ty = concrete = false; polymorphic = false; }] -(* TODO: group Bool, Char, etc. in Primitive *) -(** Generic type with regions *) -type 'r gr_ty = 'r region ty [@@deriving show, ord] +(** Ancestor for iter visitor for {!type: Types.predicates} *) +class ['self] iter_predicates_base = + object (self : 'self) + inherit [_] iter_ty + method visit_meta : 'env -> meta -> unit = fun _ _ -> () + + method visit_type_var : 'env -> type_var -> unit = + fun env x -> + let { index; name } : type_var = x in + self#visit_type_var_id env index; + self#visit_string env name + + method visit_region_var : 'env -> region_var -> unit = + fun env x -> + let { index; name } : region_var = x in + self#visit_region_id env index; + self#visit_option self#visit_string env name + + method visit_const_generic_var : 'env -> const_generic_var -> unit = + fun env x -> + let { index; name; ty } : const_generic_var = x in + self#visit_const_generic_var_id env index; + self#visit_string env name; + self#visit_literal_type env ty + end + +(** Ancestor for map visitor for {!type: Types.ty} *) +class virtual ['self] map_predicates_base = + object (self : 'self) + inherit [_] map_ty + method visit_meta : 'env -> meta -> meta = fun _ x -> x + + method visit_region_var : 'env -> region_var -> region_var = + fun env x -> + let { index; name } : region_var = x in + let index = self#visit_region_id env index in + let name = self#visit_option self#visit_string env name in + { index; name } + + method visit_type_var : 'env -> type_var -> type_var = + fun env x -> + let { index; name } : type_var = x in + let index = self#visit_type_var_id env index in + let name = self#visit_string env name in + { index; name } + + method visit_const_generic_var + : 'env -> const_generic_var -> const_generic_var = + fun env x -> + let { index; name; ty } : const_generic_var = x in + let index = self#visit_const_generic_var_id env index in + let name = self#visit_string env name in + let ty = self#visit_literal_type env ty in + { index; name; ty } + end -(** *S*ignature types. +(** Type with erased regions (this only has an informative purpose) *) +type ety = ty - Used in function signatures and type definitions. - *) -type sty = RegionVarId.id gr_ty [@@deriving show, ord] +(** Type with non-erased regions (this only has an informative purpose) *) +and rty = ty -(** Type with *R*egions. +and trait_clause = { + clause_id : trait_clause_id; + meta : meta option; + trait_id : trait_decl_id; + clause_generics : generic_args; +} - Used to project borrows/loans inside of abstractions, during symbolic - execution. - *) -type rty = RegionId.id gr_ty [@@deriving show, ord] +and generic_params = { + regions : region_var list; + types : type_var list; + (** The type parameters can be indexed with {!Types.TypeVarId.id}. -(** Type with *E*rased regions. + See {!Identifiers.Id.mapi} for instance. + *) + const_generics : const_generic_var list; + (** The const generic parameters can be indexed with {!Types.ConstGenericVarId.id}. - Used in function bodies, "regular" value types, etc. - *) -type ety = erased_region ty [@@deriving show, ord] + See {!Identifiers.Id.mapi} for instance. + *) + trait_clauses : trait_clause list; +} + +(** ('long, 'short) means that 'long outlives 'short *) +and region_outlives = region * region + +(** (T, 'a) means that T outlives 'a *) +and type_outlives = ty * region + +and trait_type_constraint = { + trait_ref : trait_ref; + generics : generic_args; + type_name : trait_item_name; + ty : ty; +} + +and predicates = { + regions_outlive : region_outlives list; + types_outlive : type_outlives list; + trait_type_constraints : trait_type_constraint list; +} +[@@deriving + show, + ord, + visitors + { + name = "iter_predicates"; + variety = "iter"; + ancestors = [ "iter_predicates_base" ]; + nude = true (* Don't inherit {!VisitorsRuntime.iter} *); + concrete = true; + polymorphic = false; + }, + visitors + { + name = "map_predicates"; + variety = "map"; + ancestors = [ "map_predicates_base" ]; + nude = true (* Don't inherit {!VisitorsRuntime.map} *); + concrete = false; + polymorphic = false; + }] + +(** An impl path element for [name] *) +type impl_elem = { + generics : generic_params; + preds : predicates; + ty : ty; + disambiguator : Disambiguator.id; +} +[@@deriving show, ord] + +(** A path element for [name] *) +type path_elem = PeIdent of string * Disambiguator.id | PeImpl of impl_elem +[@@deriving show, ord] + +(** A name *) +type name = path_elem list [@@deriving show, ord] + +(** A group of regions. + + Results from a lifetime analysis: we group the regions with the same + lifetime together, and compute the hierarchy between the regions. + This is necessary to introduce the proper abstraction with the + proper constraints, when evaluating a function call in symbolic mode. +*) +type 'id g_region_group = { + id : 'id; + regions : RegionId.id list; + parents : 'id list; +} +[@@deriving show] + +type region_group = RegionGroupId.id g_region_group [@@deriving show] +type region_groups = region_group list [@@deriving show] -type field = { meta : meta; field_name : string option; field_ty : sty } +type field = { meta : meta; field_name : string option; field_ty : ty } [@@deriving show] type variant = { @@ -332,14 +521,10 @@ type type_decl_kind = type type_decl = { def_id : TypeDeclId.id; meta : meta; - name : type_name; - region_params : region_var list; - type_params : type_var list; - const_generic_params : const_generic_var list; + is_local : bool; + name : name; + generics : generic_params; + preds : predicates; kind : type_decl_kind; - regions_hierarchy : region_var_groups; - (** Stores the hierarchy between the regions (which regions have the - same lifetime, which lifetime should end before which other lifetime, - etc.) *) } [@@deriving show] diff --git a/charon-ml/src/TypesUtils.ml b/charon-ml/src/TypesUtils.ml index 20a23472..e49a2d96 100644 --- a/charon-ml/src/TypesUtils.ml +++ b/charon-ml/src/TypesUtils.ml @@ -1,6 +1,25 @@ +open Collections open Types open Utils +module RegionOrderedType : OrderedType with type t = region = struct + type t = region + + let compare = compare_region + let to_string = show_region + let pp_t = pp_region + let show_t = show_region +end + +module RegionMap = Collections.MakeMap (RegionOrderedType) +module RegionSet = Collections.MakeSet (RegionOrderedType) + +let to_name (ls : string list) : name = + List.map (fun s -> PeIdent (s, Disambiguator.zero)) ls + +let as_ident (e : path_elem) : string = + match e with PeIdent (s, _) -> s | _ -> raise (Failure "Unexpected") + let type_decl_is_opaque (d : type_decl) : bool = match d.kind with Struct _ | Enum _ -> false | Opaque -> true @@ -27,82 +46,133 @@ let type_decl_get_fields (def : type_decl) let type_decl_is_enum (def : type_decl) : bool = match def.kind with Struct _ -> false | Enum _ -> true | Opaque -> false -(** Return [true] if a {!type: Types.ty} is actually [unit] *) -let ty_is_unit (ty : 'r ty) : bool = - match ty with Adt (Tuple, [], [], []) -> true | _ -> false +(** Return [true] if a {!type:Charon.Types.ty} is actually [unit] *) +let ty_is_unit (ty : ty) : bool = + match ty with + | TAdt + (TTuple, { regions = []; types = []; const_generics = []; trait_refs = _ }) + -> + true + | _ -> false -let ty_is_adt (ty : 'r ty) : bool = - match ty with Adt (_, _, _, _) -> true | _ -> false +let ty_is_adt (ty : ty) : bool = + match ty with TAdt (_, _) -> true | _ -> false -let ty_as_adt (ty : 'r ty) : type_id * 'r list * 'r ty list * const_generic list - = +let ty_as_adt (ty : ty) : type_id * generic_args = match ty with - | Adt (id, regions, tys, cgs) -> (id, regions, tys, cgs) + | TAdt (id, generics) -> (id, generics) | _ -> raise (Failure "Unreachable") -let ty_as_ref (ty : 'r ty) : 'r * 'r ty * ref_kind = +let ty_as_ref (ty : ty) : region * ty * ref_kind = match ty with - | Ref (r, ref_ty, kind) -> (r, ref_ty, kind) + | TRef (r, ref_ty, kind) -> (r, ref_ty, kind) | _ -> raise (Failure "Unreachable") -let ty_is_custom_adt (ty : 'r ty) : bool = - match ty with Adt (AdtId _, _, _, _) -> true | _ -> false +let ty_is_custom_adt (ty : ty) : bool = + match ty with TAdt (TAdtId _, _) -> true | _ -> false -let ty_as_custom_adt (ty : 'r ty) : - TypeDeclId.id * 'r list * 'r ty list * const_generic list = +let ty_as_custom_adt (ty : ty) : TypeDeclId.id * generic_args = match ty with - | Adt (AdtId id, regions, tys, cgs) -> (id, regions, tys, cgs) + | TAdt (TAdtId id, generics) -> (id, generics) | _ -> raise (Failure "Unreachable") -let ty_as_literal (ty : 'r ty) : literal_type = - match ty with Literal lty -> lty | _ -> raise (Failure "Unreachable") +let ty_as_literal (ty : ty) : literal_type = + match ty with TLiteral lty -> lty | _ -> raise (Failure "Unreachable") + +let const_generic_as_literal (cg : const_generic) : Values.literal = + match cg with CgValue v -> v | _ -> raise (Failure "Unreachable") -let const_generic_as_literal (cg : const_generic) : PrimitiveValues.literal = - match cg with ConstGenericValue v -> v | _ -> raise (Failure "Unreachable") +let trait_instance_id_as_trait_impl (id : trait_instance_id) : trait_impl_id = + match id with TraitImpl id -> id | _ -> raise (Failure "Unreachable") + +let empty_generic_args : generic_args = + { regions = []; types = []; const_generics = []; trait_refs = [] } + +let mk_generic_args (regions : region list) (types : ty list) + (const_generics : const_generic list) (trait_refs : trait_ref list) : + generic_args = + { regions; types; const_generics; trait_refs } + +let mk_generic_args_from_types (types : ty list) : generic_args = + { regions = []; types; const_generics = []; trait_refs = [] } + +let empty_generic_params : generic_params = + { regions = []; types = []; const_generics = []; trait_clauses = [] } + +let empty_predicates : predicates = + { regions_outlive = []; types_outlive = []; trait_type_constraints = [] } + +let merge_generic_args (g1 : generic_args) (g2 : generic_args) : generic_args = + let { regions = r1; types = tys1; const_generics = cgs1; trait_refs = tr1 } = + g1 + in + let { regions = r2; types = tys2; const_generics = cgs2; trait_refs = tr2 } = + g2 + in + { + regions = r1 @ r2; + types = tys1 @ tys2; + const_generics = cgs1 @ cgs2; + trait_refs = tr1 @ tr2; + } (** The unit type *) -let mk_unit_ty : 'r ty = Adt (Tuple, [], [], []) +let mk_unit_ty : ty = TAdt (TTuple, empty_generic_args) (** The usize type *) -let mk_usize_ty : 'r ty = Literal (Integer Usize) +let mk_usize_ty : ty = TLiteral (TInteger Usize) (** Deconstruct a type of the form [Box] to retrieve the [T] inside *) -let ty_get_box (box_ty : ety) : ety = +let ty_get_box (box_ty : ty) : ty = match box_ty with - | Adt (Assumed Box, [], [ boxed_ty ], []) -> boxed_ty + | TAdt (TAssumed TBox, { types = [ boxed_ty ]; _ }) -> boxed_ty | _ -> raise (Failure "Not a boxed type") (** Deconstruct a type of the form [&T] or [&mut T] to retrieve the [T] (and the borrow kind, etc.) *) -let ty_get_ref (ty : 'r ty) : 'r * 'r ty * ref_kind = +let ty_get_ref (ty : ty) : region * ty * ref_kind = match ty with - | Ref (r, ty, ref_kind) -> (r, ty, ref_kind) + | TRef (r, ty, ref_kind) -> (r, ty, ref_kind) | _ -> raise (Failure "Not a ref type") -let mk_ref_ty (r : 'r) (ty : 'r ty) (ref_kind : ref_kind) : 'r ty = - Ref (r, ty, ref_kind) +let mk_ref_ty (r : region) (ty : ty) (ref_kind : ref_kind) : ty = + TRef (r, ty, ref_kind) (** Make a box type *) -let mk_box_ty (ty : 'r ty) : 'r ty = Adt (Assumed Box, [], [ ty ], []) +let mk_box_ty (ty : ty) : ty = + TAdt (TAssumed TBox, mk_generic_args_from_types [ ty ]) + +(** Check if a region is in a set of regions. -(** Make a vec type *) -let mk_vec_ty (ty : 'r ty) : 'r ty = Adt (Assumed Vec, [], [ ty ], []) + This function should be used on non erased region. For sanity, we raise + an exception if the region is erased. + *) +let region_in_set (r : region) (rset : RegionId.Set.t) : bool = + match r with + | RStatic -> false + | RErased -> + raise (Failure "region_in_set shouldn't be called on erased regions") + | RVar id -> RegionId.Set.mem id rset -(** Check if a region is in a set of regions *) -let region_in_set (r : RegionId.id region) (rset : RegionId.Set.t) : bool = - match r with Static -> false | Var id -> RegionId.Set.mem id rset +(** Return the set of regions in an type - TODO: add static? -(** Return the set of regions in an rty *) -let rty_regions (ty : rty) : RegionId.Set.t = + This function should be used on non erased region. For sanity, we raise + an exception if the region is erased. + *) +let ty_regions (ty : ty) : RegionId.Set.t = let s = ref RegionId.Set.empty in - let add_region (r : RegionId.id region) = - match r with Static -> () | Var rid -> s := RegionId.Set.add rid !s + let add_region (r : region) = + match r with + | RStatic -> () (* TODO: static? *) + | RErased -> + raise (Failure "ty_regions shouldn't be called on erased regions") + | RVar rid -> s := RegionId.Set.add rid !s in let obj = object inherit [_] iter_ty - method! visit_'r _env r = add_region r + method! visit_region _env r = add_region r end in (* Explore the type *) @@ -110,45 +180,17 @@ let rty_regions (ty : rty) : RegionId.Set.t = (* Return the set of accumulated regions *) !s -let rty_regions_intersect (ty : rty) (regions : RegionId.Set.t) : bool = - let ty_regions = rty_regions ty in +(* TODO: merge with ty_has_regions_in_set *) +let ty_regions_intersect (ty : ty) (regions : RegionId.Set.t) : bool = + let ty_regions = ty_regions ty in not (RegionId.Set.disjoint ty_regions regions) -(** Convert an {!Types.ety}, containing no region variables, to an {!Types.rty} - or an {!Types.sty}. - - In practice, it is the identity. - *) -let rec ety_no_regions_to_gr_ty (ty : ety) : 'a gr_ty = - match ty with - | Adt (type_id, regions, tys, cgs) -> - assert (regions = []); - Adt (type_id, [], List.map ety_no_regions_to_gr_ty tys, cgs) - | TypeVar v -> TypeVar v - | Literal ty -> Literal ty - | Never -> Never - | Ref (_, _, _) -> - raise - (Failure - "Can't convert a ref with erased regions to a ref with non-erased \ - regions") - -let ety_no_regions_to_rty (ty : ety) : rty = ety_no_regions_to_gr_ty ty -let ety_no_regions_to_sty (ty : ety) : sty = ety_no_regions_to_gr_ty ty - -(** Check if a {!type: Types.ty} contains regions from a given set *) -let ty_has_regions_in_set (rset : RegionId.Set.t) (ty : rty) : bool = +(** Check if a {!type:Charon.Types.ty} contains regions from a given set *) +let ty_has_regions_in_set (rset : RegionId.Set.t) (ty : ty) : bool = let obj = object - inherit [_] iter_ty as super - - method! visit_Adt env type_id regions tys = - List.iter (fun r -> if region_in_set r rset then raise Found) regions; - super#visit_Adt env type_id regions tys - - method! visit_Ref env r ty rkind = - if region_in_set r rset then raise Found - else super#visit_Ref env r ty rkind + inherit [_] iter_ty + method! visit_region _ r = if region_in_set r rset then raise Found end in try @@ -162,18 +204,23 @@ let ty_has_regions_in_set (rset : RegionId.Set.t) (ty : rty) : bool = * require calling dedicated functions defined through the [Copy] trait. It * is the case for types like integers, shared borrows, etc. * - * Generally, ADTs are not primitively copyable. However, some of the primitive - * ADTs are (e.g., [Option]). + * Generally, ADTs are not primitively copyable. But some ADTs from the standard + * library like [Option] are. As it is not easy to check which external ADTs + * are primitively copyable (we would need to perform a lookup of the ADT + * definition and check its name, for instance) we don't fully check it. *) -let rec ty_is_primitively_copyable (ty : 'r ty) : bool = +let rec ty_is_primitively_copyable (ty : ty) : bool = match ty with - | Adt (Assumed Option, _, tys, _) -> - List.for_all ty_is_primitively_copyable tys - | Adt ((AdtId _ | Assumed (Box | Vec | Str | Slice | Range)), _, _, _) -> + | TAdt (TAdtId _, generics) -> + List.for_all ty_is_primitively_copyable generics.types + | TAdt (TAssumed (TBox | TStr | TSlice), _) -> false + | TAdt ((TTuple | TAssumed TArray), generics) -> + List.for_all ty_is_primitively_copyable generics.types + | TVar _ | TNever -> false + | TLiteral (TBool | TChar | TInteger _) -> true + | TTraitType _ | TArrow (_, _) -> false + | TRef (_, _, RMut) -> false + | TRef (_, _, RShared) -> true + | TRawPtr (_, _) -> + (* Not sure what to do here, so being conservative *) false - | Adt ((Tuple | Assumed Array), _, tys, _) -> - List.for_all ty_is_primitively_copyable tys - | TypeVar _ | Never -> false - | Literal (Bool | Char | Integer _) -> true - | Ref (_, _, Mut) -> false - | Ref (_, _, Shared) -> true diff --git a/charon-ml/src/UllbcAst.ml b/charon-ml/src/UllbcAst.ml index fcb1e1f4..0c9bb0e3 100644 --- a/charon-ml/src/UllbcAst.ml +++ b/charon-ml/src/UllbcAst.ml @@ -1,10 +1,9 @@ include GAst open Types -open PrimitiveValues +open Values open Expressions open Meta open Identifiers -open Names module BlockId = IdGen () (** We define this type to control the name of the visitor functions @@ -119,15 +118,7 @@ type expr_body = blocks gexpr_body [@@deriving show] type fun_body = expr_body [@@deriving show] type fun_decl = blocks gfun_decl [@@deriving show] type global_body = expr_body [@@deriving show] - -type global_decl = { - meta : meta; - def_id : GlobalDeclId.id; - name : global_name; - ty : ety; - body : global_body option; -} -[@@deriving show] +type global_decl = global_body option gglobal_decl [@@deriving show] (** ULLBC crate *) -type crate = (fun_decl, global_decl) gcrate +type crate = (blocks, global_body option) gcrate diff --git a/charon-ml/src/UllbcOfJson.ml b/charon-ml/src/UllbcOfJson.ml index 7c0cda85..cce124ea 100644 --- a/charon-ml/src/UllbcOfJson.ml +++ b/charon-ml/src/UllbcOfJson.ml @@ -5,96 +5,98 @@ include GAstOfJson open OfJsonBasic -module A = UllbcAst +open Types +open Expressions +open UllbcAst let rec statement_of_json (id_to_file : id_to_file_map) (js : json) : - (A.statement, string) result = + (statement, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("meta", meta); ("content", content) ] -> let* meta = meta_of_json id_to_file meta in let* content = raw_statement_of_json content in - Ok ({ meta; content } : A.statement) + Ok ({ meta; content } : statement) | _ -> Error "") -and raw_statement_of_json (js : json) : (A.raw_statement, string) result = +and raw_statement_of_json (js : json) : (raw_statement, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Assign", `List [ place; rvalue ]) ] -> let* place = place_of_json place in let* rvalue = rvalue_of_json rvalue in - Ok (A.Assign (place, rvalue)) + Ok (Assign (place, rvalue)) | `Assoc [ ("FakeRead", place) ] -> let* place = place_of_json place in - Ok (A.FakeRead place) + Ok (FakeRead place) | `Assoc [ ("SetDiscriminant", `List [ place; variant_id ]) ] -> let* place = place_of_json place in - let* variant_id = T.VariantId.id_of_json variant_id in - Ok (A.SetDiscriminant (place, variant_id)) + let* variant_id = VariantId.id_of_json variant_id in + Ok (SetDiscriminant (place, variant_id)) | `Assoc [ ("StorageDead", var_id) ] -> - let* var_id = E.VarId.id_of_json var_id in - Ok (A.StorageDead var_id) + let* var_id = VarId.id_of_json var_id in + Ok (StorageDead var_id) | `Assoc [ ("Deinit", place) ] -> let* place = place_of_json place in - Ok (A.Deinit place) + Ok (Deinit place) | _ -> Error "") -let switch_of_json (js : json) : (A.switch, string) result = +let switch_of_json (js : json) : (switch, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("If", `List [ id0; id1 ]) ] -> - let* id0 = A.BlockId.id_of_json id0 in - let* id1 = A.BlockId.id_of_json id1 in - Ok (A.If (id0, id1)) + let* id0 = BlockId.id_of_json id0 in + let* id1 = BlockId.id_of_json id1 in + Ok (If (id0, id1)) | `Assoc [ ("SwitchInt", `List [ int_ty; tgts; otherwise ]) ] -> let* int_ty = integer_type_of_json int_ty in let* tgts = list_of_json - (pair_of_json scalar_value_of_json A.BlockId.id_of_json) + (pair_of_json scalar_value_of_json BlockId.id_of_json) tgts in - let* otherwise = A.BlockId.id_of_json otherwise in - Ok (A.SwitchInt (int_ty, tgts, otherwise)) + let* otherwise = BlockId.id_of_json otherwise in + Ok (SwitchInt (int_ty, tgts, otherwise)) | _ -> Error "") -let call_of_json (js : json) : (A.raw_terminator, string) result = +let call_of_json (js : json) : (raw_terminator, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("call", call); ("target", target) ] -> let* call = call_of_json call in - let* target = A.BlockId.id_of_json target in + let* target = BlockId.id_of_json target in - Ok (A.Call (call, target)) + Ok (Call (call, target)) | _ -> Error "") let rec terminator_of_json (id_to_file : id_to_file_map) (js : json) : - (A.terminator, string) result = + (terminator, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("meta", meta); ("content", content) ] -> let* meta = meta_of_json id_to_file meta in let* content = raw_terminator_of_json content in - Ok ({ meta; content } : A.terminator) + Ok ({ meta; content } : terminator) | _ -> Error "") -and raw_terminator_of_json (js : json) : (A.raw_terminator, string) result = +and raw_terminator_of_json (js : json) : (raw_terminator, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("Goto", `Assoc [ ("target", target) ]) ] -> - let* target = A.BlockId.id_of_json target in - Ok (A.Goto target) + let* target = BlockId.id_of_json target in + Ok (Goto target) | `Assoc [ ("Switch", `Assoc [ ("discr", discr); ("targets", targets) ]) ] -> let* discr = operand_of_json discr in let* targets = switch_of_json targets in - Ok (A.Switch (discr, targets)) - | `String "Panic" -> Ok A.Panic - | `String "Return" -> Ok A.Return - | `String "Unreachable" -> Ok A.Unreachable + Ok (Switch (discr, targets)) + | `String "Panic" -> Ok Panic + | `String "Return" -> Ok Return + | `String "Unreachable" -> Ok Unreachable | `Assoc [ ("Drop", `Assoc [ ("place", place); ("target", target) ]) ] -> let* place = place_of_json place in - let* target = A.BlockId.id_of_json target in - Ok (A.Drop (place, target)) + let* target = BlockId.id_of_json target in + Ok (Drop (place, target)) | `Assoc [ ("Call", call) ] -> call_of_json call | `Assoc [ @@ -104,12 +106,12 @@ and raw_terminator_of_json (js : json) : (A.raw_terminator, string) result = ] -> let* cond = operand_of_json cond in let* expected = bool_of_json expected in - let* target = A.BlockId.id_of_json target in - Ok (A.Assert ({ cond; expected }, target)) + let* target = BlockId.id_of_json target in + Ok (Assert ({ cond; expected }, target)) | _ -> Error "") let block_of_json (id_to_file : id_to_file_map) (js : json) : - (A.block, string) result = + (block, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc [ ("statements", statements); ("terminator", terminator) ] -> @@ -117,29 +119,25 @@ let block_of_json (id_to_file : id_to_file_map) (js : json) : list_of_json (statement_of_json id_to_file) statements in let* terminator = terminator_of_json id_to_file terminator in - Ok { A.statements; terminator } + Ok { statements; terminator } | _ -> Error "") let blocks_of_json (id_to_file : id_to_file_map) (js : json) : - (A.block list, string) result = + (block list, string) result = combine_error_msgs js __FUNCTION__ (list_of_json (block_of_json id_to_file) js) let fun_decl_of_json (id_to_file : id_to_file_map) (js : json) : - (A.fun_decl, string) result = + (fun_decl, string) result = combine_error_msgs js __FUNCTION__ (gfun_decl_of_json (blocks_of_json id_to_file) id_to_file js) let global_decl_of_json (id_to_file : id_to_file_map) (js : json) : - (A.global_decl, string) result = + (global_decl, string) result = combine_error_msgs js __FUNCTION__ - (let* global = - gglobal_decl_of_json (blocks_of_json id_to_file) id_to_file js - in - let { def_id = global_id; meta; body; name; ty } = global in - Ok { A.def_id = global_id; meta; body; name; ty }) + (gglobal_decl_of_json (blocks_of_json id_to_file) id_to_file js) -let crate_of_json (js : json) : (A.crate, string) result = +let crate_of_json (js : json) : (crate, string) result = combine_error_msgs js __FUNCTION__ (match js with | `Assoc @@ -150,6 +148,8 @@ let crate_of_json (js : json) : (A.crate, string) result = ("types", types); ("functions", functions); ("globals", globals); + ("trait_decls", trait_decls); + ("trait_impls", trait_impls); ] -> let* name = string_of_json name in let* id_to_file = id_to_file_of_json id_to_file in @@ -159,17 +159,40 @@ let crate_of_json (js : json) : (A.crate, string) result = let* types = list_of_json (type_decl_of_json id_to_file) types in let* functions = list_of_json (fun_decl_of_json id_to_file) functions in let* globals = list_of_json (global_decl_of_json id_to_file) globals in - let types = - T.TypeDeclId.Map.of_list - (List.map (fun (d : T.type_decl) -> (d.def_id, d)) types) + let* trait_decls = + list_of_json (trait_decl_of_json id_to_file) trait_decls in - let functions = - A.FunDeclId.Map.of_list - (List.map (fun (d : A.fun_decl) -> (d.def_id, d)) functions) + let* trait_impls = + list_of_json (trait_impl_of_json id_to_file) trait_impls in - let globals = - A.GlobalDeclId.Map.of_list - (List.map (fun (d : A.global_decl) -> (d.def_id, d)) globals) + let type_decls = + TypeDeclId.Map.of_list + (List.map (fun (d : type_decl) -> (d.def_id, d)) types) in - Ok { A.name; declarations; types; functions; globals } + let fun_decls = + FunDeclId.Map.of_list + (List.map (fun (d : fun_decl) -> (d.def_id, d)) functions) + in + let global_decls = + GlobalDeclId.Map.of_list + (List.map (fun (d : global_decl) -> (d.def_id, d)) globals) + in + let trait_decls = + TraitDeclId.Map.of_list + (List.map (fun (d : trait_decl) -> (d.def_id, d)) trait_decls) + in + let trait_impls = + TraitImplId.Map.of_list + (List.map (fun (d : trait_impl) -> (d.def_id, d)) trait_impls) + in + Ok + { + name; + declarations; + type_decls; + fun_decls; + global_decls; + trait_decls; + trait_impls; + } | _ -> Error "") diff --git a/charon-ml/src/PrimitiveValues.ml b/charon-ml/src/Values.ml similarity index 53% rename from charon-ml/src/PrimitiveValues.ml rename to charon-ml/src/Values.ml index 53b3ecc7..3367a84e 100644 --- a/charon-ml/src/PrimitiveValues.ml +++ b/charon-ml/src/Values.ml @@ -20,121 +20,66 @@ let pp_big_int (fmt : Format.formatter) (bi : big_int) : unit = let compare_big_int (bi0 : big_int) (bi1 : big_int) : int = Z.compare bi0 bi1 let show_big_int (bi : big_int) : string = Z.to_string bi -type integer_type = - | Isize - | I8 - | I16 - | I32 - | I64 - | I128 - | Usize - | U8 - | U16 - | U32 - | U64 - | U128 -[@@deriving show, ord] - -(** Ancestor the literal_type iter visitor *) -class ['self] iter_literal_type_base = +(** Ancestor the literal iter visitor *) +class ['self] iter_literal_base = object (_self : 'self) inherit [_] VisitorsRuntime.iter - method visit_integer_type : 'env -> integer_type -> unit = fun _ _ -> () + method visit_big_int : 'env -> big_int -> unit = fun _ _ -> () end -(** Ancestor the literal_type map visitor *) -class ['self] map_literal_type_base = +(** Ancestor the literal map visitor *) +class ['self] map_literal_base = object (_self : 'self) inherit [_] VisitorsRuntime.map - - method visit_integer_type : 'env -> integer_type -> integer_type = - fun _ x -> x + method visit_big_int : 'env -> big_int -> big_int = fun _ x -> x end -(** Ancestor the literal_type reduce visitor *) -class virtual ['self] reduce_literal_type_base = +(** Ancestor the literal reduce visitor *) +class virtual ['self] reduce_literal_base = object (self : 'self) inherit [_] VisitorsRuntime.reduce - - method visit_integer_type : 'env -> integer_type -> 'a = - fun _ _ -> self#zero + method visit_big_int : 'env -> big_int -> 'a = fun _ _ -> self#zero end -(** Ancestor the literal_type mapreduce visitor *) -class virtual ['self] mapreduce_literal_type_base = +(** Ancestor the literal mapreduce visitor *) +class virtual ['self] mapreduce_literal_base = object (self : 'self) inherit [_] VisitorsRuntime.mapreduce - method visit_integer_type : 'env -> integer_type -> integer_type * 'a = + method visit_big_int : 'env -> big_int -> big_int * 'a = fun _ x -> (x, self#zero) end -(* TODO: make literal_type consistent with literal: "integer" or "scalar" *) -type literal_type = Integer of integer_type | Bool | Char -[@@deriving - show, - ord, - visitors - { - name = "iter_literal_type"; - variety = "iter"; - ancestors = [ "iter_literal_type_base" ]; - nude = true; - concrete = true; - }, - visitors - { - name = "map_literal_type"; - variety = "map"; - ancestors = [ "map_literal_type_base" ]; - nude = true; - concrete = true; - }, - visitors - { - name = "reduce_literal_type"; - variety = "reduce"; - ancestors = [ "reduce_literal_type_base" ]; - nude = true; - }, - visitors - { - name = "mapreduce_literal_type"; - variety = "mapreduce"; - ancestors = [ "mapreduce_literal_type_base" ]; - nude = true; - }] +(* TODO: make literal consistent with literal: "integer" or "scalar" *) +type integer_type = + | Isize + | I8 + | I16 + | I32 + | I64 + | I128 + | Usize + | U8 + | U16 + | U32 + | U64 + | U128 + +and literal_type = TInteger of integer_type | TBool | TChar (** A scalar value Note that we use unbounded integers everywhere. We then harcode the boundaries for the different types. *) -type scalar_value = { value : big_int; int_ty : integer_type } -[@@deriving show, ord] - -(** Ancestor the literal iter visitor *) -class ['self] iter_literal_base = - object (_self : 'self) - inherit [_] VisitorsRuntime.iter - method visit_scalar_value : 'env -> scalar_value -> unit = fun _ _ -> () - end - -(** Ancestor the literal map visitor *) -class ['self] map_literal_base = - object (_self : 'self) - inherit [_] VisitorsRuntime.map - - method visit_scalar_value : 'env -> scalar_value -> scalar_value = - fun _ x -> x - end +and scalar_value = { value : big_int; int_ty : integer_type } (** A literal value. Can be used by operands (in which case it represents a constant) or by the interpreter to represent a concrete, literal value. *) -type literal = Scalar of scalar_value | Bool of bool | Char of char +and literal = VScalar of scalar_value | VBool of bool | VChar of char [@@deriving show, ord, @@ -153,4 +98,18 @@ type literal = Scalar of scalar_value | Bool of bool | Char of char ancestors = [ "map_literal_base" ]; nude = true; concrete = true; + }, + visitors + { + name = "reduce_literal"; + variety = "reduce"; + ancestors = [ "reduce_literal_base" ]; + nude = true; + }, + visitors + { + name = "mapreduce_literal"; + variety = "mapreduce"; + ancestors = [ "mapreduce_literal_base" ]; + nude = true; }] diff --git a/charon-ml/src/ValuesUtils.ml b/charon-ml/src/ValuesUtils.ml new file mode 100644 index 00000000..bd2827a7 --- /dev/null +++ b/charon-ml/src/ValuesUtils.ml @@ -0,0 +1,7 @@ +open Values + +let literal_as_scalar (v : literal) : scalar_value = + match v with VScalar v -> v | _ -> raise (Failure "Unexpected") + +let literal_type_is_integer (t : literal_type) : bool = + match t with TInteger _ -> true | _ -> false diff --git a/charon-ml/src/dune b/charon-ml/src/dune index b7dc3d6d..f824a703 100644 --- a/charon-ml/src/dune +++ b/charon-ml/src/dune @@ -3,7 +3,7 @@ (public_name charon) ;; The name as revealed to the projects importing this library (preprocess (pps ppx_deriving.show ppx_deriving.ord visitors.ppx)) - (libraries yojson zarith easy_logging) + (libraries yojson zarith easy_logging name_matcher_parser) (modules Collections Expressions @@ -18,24 +18,25 @@ Logging Meta MetaUtils - Names + NameMatcher OfJsonBasic PrintExpressions PrintGAst PrintLlbcAst PrintUllbcAst - PrintPrimitiveValues PrintTypes PrintUtils - PrimitiveValues - PrimitiveValuesUtils + PrintValues Scalars + StringUtils Types TypesUtils UllbcAst UllbcAstUtils UllbcOfJson - Utils)) + Utils + Values + ValuesUtils)) (documentation (package charon)) @@ -48,7 +49,7 @@ -g ;-dsource -warn-error - -5-8-9-11-14-33-20-21-26-27-39)) + -5@8-11-14-33-20-21-26-27-39)) (release (flags :standard @@ -56,4 +57,4 @@ -g ;-dsource -warn-error - -5-8-9-11-14-33-20-21-26-27-39))) + -5@8-11-14-33-20-21-26-27-39))) diff --git a/charon-ml/tests/Test_NameMatcher.ml b/charon-ml/tests/Test_NameMatcher.ml new file mode 100644 index 00000000..9182ff27 --- /dev/null +++ b/charon-ml/tests/Test_NameMatcher.ml @@ -0,0 +1,52 @@ +open Charon.NameMatcher + +let parse_tests () = + let patterns : string list = + [ + "x"; + "x::y"; + "x::y1::Z2::a"; + "{@T}"; + "{@1}"; + "x::{@T}"; + "x::{[@T]}"; + "x::{[@T; @N]}"; + "x::{&'R @T1}"; + "x::{&'R mut @T1}"; + "{Box<@T>}"; + "alloc::{Box<@T>}::new"; + "alloc::{Foo<@T, @C>}::new"; + "core::slice::index::SliceIndex<@T, @I>"; + "core::slice::index::SliceIndex"; + "core::slice::index::SliceIndex"; + "core::slice::index::SliceIndex, [@T]>"; + "{()}"; + "{(@T, @T, Range)}"; + ] + in + let _ = List.map parse_pattern patterns in + () + +let name_map_tests () = + let bindings = + [ + "a"; + "a::b::{Type<@>}"; + "a::b::{Type<@T>}::c"; + "a::b::{Type<@>}::d"; + "a::b::{Type<@1>}::d::e"; + "a::b"; + "a::c"; + "a::{Type1<'a, @T>}::h"; + "a::{Type1<'b, @T>}::e"; + ] + in + let bindings = List.mapi (fun i p -> (parse_pattern p, i)) bindings in + let m = NameMatcherMap.of_list bindings in + List.iter + (fun (p, i) -> assert (snd (NameMatcherMap.replace p (-1) m) = Some i)) + bindings + +let run_tests () = + parse_tests (); + name_map_tests () diff --git a/charon-ml/tests/Tests.ml b/charon-ml/tests/Tests.ml index 9326f6db..9acd945a 100644 --- a/charon-ml/tests/Tests.ml +++ b/charon-ml/tests/Tests.ml @@ -13,3 +13,4 @@ let () = (* Call the tests *) let () = Test_Deserialize.run_tests "../../../tests/serialized" +let () = Test_NameMatcher.run_tests () diff --git a/charon-ml/tests/dune b/charon-ml/tests/dune index 29efb38e..92ac8269 100644 --- a/charon-ml/tests/dune +++ b/charon-ml/tests/dune @@ -1,4 +1,4 @@ (tests (names Tests) - (modules Tests Test_Deserialize) + (modules Tests Test_Deserialize Test_NameMatcher) (libraries charon)) diff --git a/charon/Cargo.lock b/charon/Cargo.lock index e8c2eb48..9dd3d7cb 100644 --- a/charon/Cargo.lock +++ b/charon/Cargo.lock @@ -2,11 +2,37 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adt-into" +version = "0.1.0" +source = "git+https://github.com/hacspec/hacspec-v2.git?branch=charon_traits#9d6e4953130330405bcbd2d98815f5b058731ba4" +dependencies = [ + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -15,9 +41,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -57,7 +83,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -68,12 +94,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "bitmaps" version = "2.1.0" @@ -96,9 +143,18 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -111,12 +167,17 @@ name = "charon" version = "0.1.0" dependencies = [ "assert_cmd", + "backtrace", "bumpalo", + "colored", + "derivative", "env_logger", "hashlink", + "hax-frontend-exporter", + "hax-frontend-exporter-options", "heck", "im", - "itertools", + "itertools 0.10.5", "lazy_static", "linked_hash_set", "log", @@ -143,13 +204,35 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "difflib" version = "0.4.0" @@ -162,11 +245,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" @@ -181,6 +270,22 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -189,15 +294,21 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "hashbrown" version = "0.11.2" @@ -209,9 +320,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "hashlink" @@ -222,6 +333,31 @@ dependencies = [ "hashbrown 0.11.2", ] +[[package]] +name = "hax-frontend-exporter" +version = "0.1.0" +source = "git+https://github.com/hacspec/hacspec-v2.git?branch=charon_traits#9d6e4953130330405bcbd2d98815f5b058731ba4" +dependencies = [ + "adt-into", + "hax-frontend-exporter-options", + "itertools 0.11.0", + "paste", + "schemars", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "hax-frontend-exporter-options" +version = "0.1.0" +source = "git+https://github.com/hacspec/hacspec-v2.git?branch=charon_traits#9d6e4953130330405bcbd2d98815f5b058731ba4" +dependencies = [ + "schemars", + "serde", + "serde_json", +] + [[package]] name = "heck" version = "0.3.3" @@ -240,6 +376,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "humantime" version = "2.1.0" @@ -262,12 +404,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown 0.14.2", ] [[package]] @@ -279,6 +421,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -288,11 +441,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" @@ -302,9 +464,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linked-hash-map" @@ -321,11 +483,17 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -333,9 +501,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "macros" @@ -349,9 +517,18 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] [[package]] name = "multimap" @@ -362,6 +539,15 @@ dependencies = [ "serde", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -393,16 +579,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", "indexmap", ] +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + [[package]] name = "predicates" version = "2.1.5" @@ -410,7 +608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", - "itertools", + "itertools 0.10.5", "predicates-core", ] @@ -466,18 +664,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -503,18 +701,18 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.2", + "regex-automata 0.4.3", "regex-syntax", ] @@ -526,9 +724,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -537,9 +735,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_tools_util" @@ -547,11 +751,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598f48ce2a421542b3e64828aa742b687cc1b91d2f96591cfdb7ac5988cd6366" +[[package]] +name = "rustix" +version = "0.38.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -562,37 +779,72 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.171" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.39", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -633,9 +885,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "strsim" @@ -680,9 +932,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -697,9 +949,9 @@ checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -728,6 +980,37 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -736,15 +1019,15 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" @@ -754,9 +1037,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "vec_map" @@ -781,9 +1064,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -813,9 +1096,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -825,3 +1108,69 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/charon/Cargo.toml b/charon/Cargo.toml index 8d1decd1..73acbdaf 100644 --- a/charon/Cargo.toml +++ b/charon/Cargo.toml @@ -39,6 +39,13 @@ macros = { path = "./macros" } take_mut = "0.2.2" rustc_tools_util = "0.2.1" bumpalo = "3.11.1" # We constrain the version of [bumpalo] because of a vulnerability +hax-frontend-exporter = { git = "https://github.com/hacspec/hacspec-v2.git", branch = "charon_traits" } +hax-frontend-exporter-options = { git = "https://github.com/hacspec/hacspec-v2.git", branch = "charon_traits" } +#hax-frontend-exporter = { path = "../../hacspec-v2/frontend/exporter" } +#hax-frontend-exporter-options = { path = "../../hacspec-v2/frontend/exporter/options" } +colored = "2.0.4" +derivative = "2.2.0" +backtrace = "0.3.69" [dev-dependencies] assert_cmd = "1.0.8" diff --git a/charon/Makefile b/charon/Makefile index 5413c5b4..fa020aed 100644 --- a/charon/Makefile +++ b/charon/Makefile @@ -3,7 +3,7 @@ all: build .PHONY: build build: - cargo build + cargo build --release .PHONY: test test: diff --git a/charon/macros/src/lib.rs b/charon/macros/src/lib.rs index 023ba509..ec42458b 100644 --- a/charon/macros/src/lib.rs +++ b/charon/macros/src/lib.rs @@ -116,6 +116,8 @@ pub mod {} {{ }} }} + // TODO: factor this out in a specific file, make it an immutable map to have O(1) clone + #[derive(std::fmt::Debug, std::clone::Clone)] pub struct MapGenerator {{ pub counter : Generator, // We use a btree map so that the bindings are sorted by key @@ -138,8 +140,13 @@ pub mod {} {{ }} }} - pub fn get(&self, k: K) -> Option {{ - self.map.get(&k).map(|id| *id) + pub fn get(&self, k: &K) -> Option {{ + self.map.get(k).map(|id| *id) + }} + + // We may need to generate fresh ids without inserting a value in the map + pub fn fresh_id(&mut self) -> Id {{ + self.counter.fresh_id() }} }} }}" diff --git a/charon/src/assumed.rs b/charon/src/assumed.rs index 85040efb..63e810aa 100644 --- a/charon/src/assumed.rs +++ b/charon/src/assumed.rs @@ -4,57 +4,49 @@ //! When checking whether names are equal to one of the reference names below, //! we ignore the disambiguators (see [crate::names] and [crate::names_utils]). // TODO: rename to "primitive" -#![allow(dead_code)] use crate::names::*; -use crate::types; +use crate::types::*; use crate::ullbc_ast; use macros::EnumIsA; +/// Ignore the builtin/auto traits like [core::marker::Sized] or [core::marker::Sync]. +pub const IGNORE_BUILTIN_MARKER_TRAITS: bool = true; + +// Ignored traits (includes marker traits, and others) +pub static MARKER_SIZED_NAME: [&str; 3] = ["core", "marker", "Sized"]; +pub static MARKER_TUPLE_NAME: [&str; 3] = ["core", "marker", "Tuple"]; +pub static SYNC_NAME: [&str; 3] = ["core", "marker", "SYNC"]; +pub static SEND_NAME: [&str; 3] = ["core", "marker", "SEND"]; +pub static UNPIN_NAME: [&str; 3] = ["core", "marker", "UNPIN"]; +pub static ALLOC_ALLOCATOR: [&str; 3] = ["core", "alloc", "Allocator"]; +pub static IGNORED_TRAITS_NAMES: [&[&str]; 6] = [ + &MARKER_SIZED_NAME, + &MARKER_TUPLE_NAME, + &SYNC_NAME, + &SEND_NAME, + &UNPIN_NAME, + &ALLOC_ALLOCATOR, +]; + // Assumed types pub static BOX_NAME: [&str; 3] = ["alloc", "boxed", "Box"]; -pub static VEC_NAME: [&str; 3] = ["alloc", "vec", "Vec"]; -pub static OPTION_NAME: [&str; 3] = ["core", "option", "Option"]; -pub static RANGE_NAME: [&str; 4] = ["core", "ops", "range", "Range"]; - -pub static OPTION_NONE_VARIANT_ID: types::VariantId::Id = types::VariantId::ZERO; -pub static OPTION_SOME_VARIANT_ID: types::VariantId::Id = types::VariantId::ONE; // -// Assumed functions/traits +// Assumed functions // pub static PANIC_NAME: [&str; 3] = ["core", "panicking", "panic"]; pub static BEGIN_PANIC_NAME: [&str; 3] = ["std", "panicking", "begin_panic"]; -pub static REPLACE_NAME: [&str; 3] = ["core", "mem", "replace"]; +pub static ASSERT_FAILED_NAME: [&str; 3] = ["core", "panicking", "assert_failed"]; -// Boxes -pub static BOX_NEW_NAME: [&str; 4] = ["alloc", "boxed", "Box", "new"]; -// This is a trait: for now we assume it is only used on boxes -pub static DEREF_DEREF_NAME: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; -// This is a trait: for now we assume it is only used on boxes -pub static DEREF_DEREF_MUT_NAME: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; +// Boxes - remark: there misses `Box::new` which has an impl block (TODO: remove?) +// Only Box::free needs to have a special treatment. pub static BOX_FREE_NAME: [&str; 3] = ["alloc", "alloc", "box_free"]; -// Index traits -pub static INDEX_NAME: [&str; 5] = ["core", "ops", "index", "Index", "index"]; -pub static INDEX_MUT_NAME: [&str; 5] = ["core", "ops", "index", "IndexMut", "index_mut"]; - -// Slices -pub static SLICE_LEN_NAME: [&str; 4] = ["core", "slice", "[T]", "len"]; // TODO: fix the `[T]` name element - -// Vectors -pub static VEC_NEW_NAME: [&str; 4] = ["alloc", "vec", "Vec", "new"]; -pub static VEC_PUSH_NAME: [&str; 4] = ["alloc", "vec", "Vec", "push"]; -pub static VEC_INSERT_NAME: [&str; 4] = ["alloc", "vec", "Vec", "insert"]; -pub static VEC_LEN_NAME: [&str; 4] = ["alloc", "vec", "Vec", "len"]; - // Pointers pub static PTR_UNIQUE_NAME: [&str; 3] = ["core", "ptr", "Unique"]; pub static PTR_NON_NULL_NAME: [&str; 3] = ["core", "ptr", "NonNull"]; -// We ignore this trait, which is implicitly given to all the type parameters -pub static MARKER_SIZED_NAME: [&str; 3] = ["core", "marker", "Sized"]; - /// We redefine identifiers for assumed functions here, instead of reusing the /// identifiers from [ullbc_ast], because: /// - some of the functions (the panic functions) will actually not be translated @@ -65,49 +57,36 @@ pub static MARKER_SIZED_NAME: [&str; 3] = ["core", "marker", "Sized"]; enum FunId { /// `core::panicking::panic` Panic, - /// `std::panicking::begin_panic` + /// `std::panicking::begin_panic` - TODO: remove? BeginPanic, - Replace, BoxNew, - BoxDeref, - BoxDerefMut, BoxFree, - /// `index` function of the `Index` trait - Index, - /// `index_mut` function of the `IndexMut` trait - IndexMut, - SliceLen, - VecNew, - VecPush, - VecInsert, - VecLen, } -pub fn get_type_id_from_name(name: &TypeName) -> Option { +pub fn is_marker_trait(name: &Name) -> bool { + for n in IGNORED_TRAITS_NAMES { + if name.equals_ref_name(n) { + return true; + } + } + false +} + +pub fn get_type_id_from_name(name: &Name) -> Option { if name.equals_ref_name(&BOX_NAME) { - Option::Some(types::AssumedTy::Box) - } else if name.equals_ref_name(&RANGE_NAME) { - Option::Some(types::AssumedTy::Range) - } else if name.equals_ref_name(&VEC_NAME) { - Option::Some(types::AssumedTy::Vec) - } else if name.equals_ref_name(&OPTION_NAME) { - Option::Some(types::AssumedTy::Option) + Option::Some(AssumedTy::Box) } else if name.equals_ref_name(&PTR_UNIQUE_NAME) { - Option::Some(types::AssumedTy::PtrUnique) + Option::Some(AssumedTy::PtrUnique) } else if name.equals_ref_name(&PTR_NON_NULL_NAME) { - Option::Some(types::AssumedTy::PtrNonNull) + Option::Some(AssumedTy::PtrNonNull) } else { Option::None } } -pub fn get_name_from_type_id(id: types::AssumedTy) -> Vec { - use types::AssumedTy; +pub fn get_name_from_type_id(id: AssumedTy) -> Vec { match id { AssumedTy::Box => BOX_NAME.iter().map(|s| s.to_string()).collect(), - AssumedTy::Range => RANGE_NAME.iter().map(|s| s.to_string()).collect(), - AssumedTy::Vec => VEC_NAME.iter().map(|s| s.to_string()).collect(), - AssumedTy::Option => OPTION_NAME.iter().map(|s| s.to_string()).collect(), AssumedTy::PtrUnique => PTR_UNIQUE_NAME.iter().map(|s| s.to_string()).collect(), AssumedTy::PtrNonNull => PTR_NON_NULL_NAME.iter().map(|s| s.to_string()).collect(), AssumedTy::Str => vec!["Str".to_string()], @@ -116,108 +95,58 @@ pub fn get_name_from_type_id(id: types::AssumedTy) -> Vec { } } -fn get_fun_id_from_name_full(name: &FunName) -> Option { +fn get_fun_id_from_name_full(name: &Name) -> Option { if name.equals_ref_name(&PANIC_NAME) { Option::Some(FunId::Panic) } else if name.equals_ref_name(&BEGIN_PANIC_NAME) { Option::Some(FunId::BeginPanic) - } else if name.equals_ref_name(&REPLACE_NAME) { - Option::Some(FunId::Replace) - } else if name.equals_ref_name(&BOX_NEW_NAME) { - Option::Some(FunId::BoxNew) - } else if name.equals_ref_name(&DEREF_DEREF_NAME) { - Option::Some(FunId::BoxDeref) - } else if name.equals_ref_name(&DEREF_DEREF_MUT_NAME) { - Option::Some(FunId::BoxDerefMut) } else if name.equals_ref_name(&BOX_FREE_NAME) { Option::Some(FunId::BoxFree) - } else if name.equals_ref_name(&VEC_NEW_NAME) { - Option::Some(FunId::VecNew) - } else if name.equals_ref_name(&VEC_PUSH_NAME) { - Option::Some(FunId::VecPush) - } else if name.equals_ref_name(&VEC_INSERT_NAME) { - Option::Some(FunId::VecInsert) - } else if name.equals_ref_name(&VEC_LEN_NAME) { - Option::Some(FunId::VecLen) - } else if name.equals_ref_name(&INDEX_NAME) { - Option::Some(FunId::Index) - } else if name.equals_ref_name(&INDEX_MUT_NAME) { - Option::Some(FunId::IndexMut) - } else if name.equals_ref_name(&SLICE_LEN_NAME) { - Option::Some(FunId::SliceLen) } else { - Option::None + // Box::new is peculiar because there is an impl block + use PathElem::*; + match name.name.as_slice() { + [Ident(alloc, _), Ident(boxed, _), Impl(impl_elem), Ident(new, _)] => { + if alloc == "alloc" && boxed == "boxed" && new == "new" { + match &impl_elem.ty { + Ty::Adt(TypeId::Assumed(AssumedTy::Box), generics) => { + let GenericArgs { + regions, + types, + const_generics, + trait_refs, + } = generics; + if regions.is_empty() + && types.len() == 1 + && const_generics.is_empty() + && trait_refs.is_empty() + { + match types.as_slice() { + [Ty::TypeVar(_)] => Option::Some(FunId::BoxNew), + _ => Option::None, + } + } else { + Option::None + } + } + _ => Option::None, + } + } else { + Option::None + } + } + _ => Option::None, + } } } -pub fn get_fun_id_from_name( - name: &FunName, - type_args: &Vec, -) -> Option { +pub fn get_fun_id_from_name(name: &Name) -> Option { match get_fun_id_from_name_full(name) { Option::Some(id) => { let id = match id { FunId::Panic | FunId::BeginPanic => unreachable!(), - FunId::Replace => ullbc_ast::AssumedFunId::Replace, FunId::BoxNew => ullbc_ast::AssumedFunId::BoxNew, - FunId::BoxDeref => ullbc_ast::AssumedFunId::BoxDeref, - FunId::BoxDerefMut => ullbc_ast::AssumedFunId::BoxDerefMut, FunId::BoxFree => ullbc_ast::AssumedFunId::BoxFree, - FunId::VecNew => ullbc_ast::AssumedFunId::VecNew, - FunId::VecPush => ullbc_ast::AssumedFunId::VecPush, - FunId::VecInsert => ullbc_ast::AssumedFunId::VecInsert, - FunId::VecLen => ullbc_ast::AssumedFunId::VecLen, - FunId::SliceLen => ullbc_ast::AssumedFunId::SliceLen, - FunId::Index | FunId::IndexMut => { - assert!(type_args.len() == 1); - use types::*; - - // Indexing into an array (pointer arithmetic + dereference) is represented in MIR - // by an Offset projector followed by a Deref operation. - // - // Here, we see the Index trait on arrays, which does NOT mean indexing into an - // array (like above). Instead, it refers to a particular implementation that - // demands that the index be a Range, and that returns a Slice. See: - // - https://doc.rust-lang.org/src/core/array/mod.rs.html#340 - // - https://doc.rust-lang.org/src/core/slice/index.rs.html#11 - // - https://doc.rust-lang.org/src/core/slice/index.rs.html#350 - match type_args[0] { - Ty::Adt(TypeId::Assumed(aty), ..) => { - // TODO: figure out whether indexing into a slice - // (i.e. bounds-check, pointer arithmetic, dereference) also - // appears as an implementation of the Index trait or as a - // primitive operation like array indexing. - match aty { - types::AssumedTy::Vec => { - if id.is_index() { - ullbc_ast::AssumedFunId::VecIndex - } else { - // mut case - ullbc_ast::AssumedFunId::VecIndexMut - } - } - types::AssumedTy::Array => { - if id.is_index() { - ullbc_ast::AssumedFunId::ArraySubsliceShared - } else { - // mut case - ullbc_ast::AssumedFunId::ArraySubsliceMut - } - } - types::AssumedTy::Slice => { - if id.is_index() { - ullbc_ast::AssumedFunId::SliceSubsliceShared - } else { - // mut case - ullbc_ast::AssumedFunId::SliceSubsliceMut - } - } - _ => unimplemented!("ty: {:?}", aty), - } - } - _ => unimplemented!(), - } - } }; Option::Some(id) } @@ -229,31 +158,21 @@ pub fn get_fun_id_from_name( /// assumed types. /// For instance, many types like box or vec are parameterized (in MIR) by an allocator /// (`std::alloc::Allocator`): we ignore it. -pub fn type_to_used_params(name: &TypeName) -> Option> { - trace!("{}", name); +pub fn type_to_used_params(name: &Name) -> Option> { + trace!("{:?}", name); match get_type_id_from_name(name) { Option::None => Option::None, Option::Some(id) => { - use types::AssumedTy; let id = match id { AssumedTy::Box => { vec![true, false] } - AssumedTy::Vec => { - vec![true, false] - } - AssumedTy::Option => { - vec![true] - } AssumedTy::PtrUnique | AssumedTy::PtrNonNull => { vec![true] } AssumedTy::Str => { vec![] } - AssumedTy::Range => { - vec![true] - } AssumedTy::Array | AssumedTy::Slice => vec![true], }; Option::Some(id) @@ -268,8 +187,8 @@ pub struct FunInfo { } /// See the comments for [type_to_used_params] -pub fn function_to_info(name: &FunName) -> Option { - trace!("{}", name); +pub fn function_to_info(name: &Name) -> Option { + trace!("{:?}", name); match get_fun_id_from_name_full(name) { Option::None => Option::None, Option::Some(id) => { @@ -282,56 +201,14 @@ pub fn function_to_info(name: &FunName) -> Option { used_type_params: vec![true], used_args: vec![true], }, - FunId::Replace => FunInfo { - used_type_params: vec![true], - used_args: vec![true, true], - }, FunId::BoxNew => FunInfo { used_type_params: vec![true], used_args: vec![true], }, - FunId::BoxDeref => FunInfo { - used_type_params: vec![true], - used_args: vec![true], - }, - FunId::BoxDerefMut => FunInfo { - used_type_params: vec![true], - used_args: vec![true], - }, FunId::BoxFree => FunInfo { used_type_params: vec![true, false], used_args: vec![true, false], }, - FunId::VecNew => FunInfo { - used_type_params: vec![true], - used_args: vec![], - }, - FunId::VecPush => FunInfo { - used_type_params: vec![true, false], - used_args: vec![true, true], - }, - FunId::VecInsert => FunInfo { - used_type_params: vec![true, false], - used_args: vec![true, true, true], - }, - FunId::VecLen => FunInfo { - used_type_params: vec![true, false], - used_args: vec![true], - }, - FunId::SliceLen => FunInfo { - used_type_params: vec![true], - used_args: vec![true], - }, - FunId::Index => FunInfo { - // The second type parameter is for the index type (`usize` for vectors) - used_type_params: vec![true, false], - used_args: vec![true, true], - }, - FunId::IndexMut => FunInfo { - // The second type parameter is for the index type (`usize` for vectors) - used_type_params: vec![true, false], - used_args: vec![true, true], - }, }; Option::Some(info) } diff --git a/charon/src/charon-driver.rs b/charon/src/charon-driver.rs index 8e27c62a..c25a9b90 100644 --- a/charon/src/charon-driver.rs +++ b/charon/src/charon-driver.rs @@ -1,11 +1,15 @@ //! The Charon driver, which calls Rustc with callbacks to compile some Rust //! crate to LLBC. +#![allow(dead_code)] #![feature(rustc_private, register_tool)] #![feature(box_patterns)] #![feature(cell_leak)] // For Ref::leak // For rustdoc: prevents overflows #![recursion_limit = "256"] +#![feature(trait_alias)] +#![feature(let_chains)] +#![feature(iterator_try_collect)] extern crate hashlink; extern crate im; @@ -39,11 +43,9 @@ mod driver; mod export; mod expressions; mod expressions_utils; -mod extract_global_assignments; mod formatter; mod gast; mod gast_utils; -mod generics; mod get_mir; mod graphs; mod id_map; @@ -59,17 +61,19 @@ mod names; mod names_utils; mod ops_to_function_calls; mod reconstruct_asserts; -mod regions_hierarchy; -mod regularize_constant_adts; mod remove_drop_never; mod remove_dynamic_checks; +mod remove_nops; mod remove_read_discriminant; mod remove_unused_locals; mod reorder_decls; +mod simplify_constants; mod translate_constants; mod translate_crate_to_ullbc; mod translate_ctx; mod translate_functions_to_ullbc; +mod translate_predicates; +mod translate_traits; mod translate_types; mod types; mod types_utils; @@ -203,7 +207,18 @@ fn main() { // We don't need to check this case in order to use the default Rustc callbacks // instead of the Charon callback: because there is nothing to build, Rustc will // take care of everything and actually not call us back. - RunCompiler::new(&compiler_args, &mut CharonCallbacks { options }) - .run() - .unwrap(); + let mut callback = CharonCallbacks { + options, + error_count: 0, + }; + let res = RunCompiler::new(&compiler_args, &mut callback).run(); + + match res { + Ok(()) => (), + Err(_) => { + let msg = format!("The extraction encountered {} errors", callback.error_count); + log::error!("{}", msg); + std::process::exit(1); + } + } } diff --git a/charon/src/cli_options.rs b/charon/src/cli_options.rs index 15b40560..26392dcc 100644 --- a/charon/src/cli_options.rs +++ b/charon/src/cli_options.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - /// The options received as input by cargo-charon use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -102,6 +100,30 @@ performs: `y := (x as E2).1`). Producing a better reconstruction is non-trivial. /// specific version of Cargo. #[structopt(long = "cargo-no-rust-version")] pub cargo_no_rust_version: bool, + /// Panic on the first error. This is useful for debugging. + #[structopt(long = "panic-on-error")] + pub panic_on_error: bool, + #[structopt( + long = "print-ullbc", + help = " +Print the ULLBC immediately after extraction from MIR. +" + )] + pub print_ullbc: bool, + #[structopt( + long = "print-built-llbc", + help = " +Print the LLBC just after we built it (i.e., immediately after loop reconstruction). +" + )] + pub print_built_llbc: bool, + #[structopt( + long = "print-llbc", + help = " +Print the final LLBC (after all the cleaning micro-passes). +" + )] + pub print_llbc: bool, } /// The name of the environment variable we use to save the serialized Cli options diff --git a/charon/src/common.rs b/charon/src/common.rs index 601bd510..a2027d6f 100644 --- a/charon/src/common.rs +++ b/charon/src/common.rs @@ -1,66 +1,10 @@ -#![allow(dead_code)] +pub static TAB_INCR: &str = " "; -use crate::meta; -use im::Vector; -use rustc_error_messages::MultiSpan; -use rustc_errors::DiagnosticId; -use rustc_session::Session; -use serde::{Serialize, Serializer}; -use std::iter::FromIterator; - -/// Our redefinition of Result - we don't care much about the I/O part. -pub type Result = std::result::Result; - -/// Propagate the error from a callback to the caller : -/// Used to avoid saving, checking and returning the result by hand. -/// The callback will not be called again if it returned an error. -/// The dynamic signature is used to pass a generic function as argument. -/// A simple use case is shown in `test_propagate_error`. -pub fn propagate_error(consumer: C, mut callback: F) -> Result<()> -where - F: FnMut(T) -> Result<()>, - C: FnOnce(&mut dyn FnMut(T)), -{ - let mut res = Ok(()); - consumer(&mut |arg: T| { - if res.is_ok() { - res = callback(arg); - } - }); - res -} - -/// We use both `ErrorEmitter` and the logger to report errors and warnings. -/// Those two ways of reporting information don't target the same usage and -/// the same users. -/// - `ErrorEmitter` allows us to report a limited number of messages to -/// the user, with the same formatting as the compiler messages. -/// - On the other hand, the logger allows us to report and filter a big number -/// of detailed messages, for debugging purposes. -pub trait ErrorEmitter { - fn span_err>(&self, s: S, msg: &'static str); - - fn span_warn>(&self, s: S, msg: &'static str); -} - -impl ErrorEmitter for Session { - fn span_err>(&self, s: S, msg: &'static str) { - self.span_err_with_code(s, msg, DiagnosticId::Error(String::from("Aeneas"))); - } - - fn span_warn>(&self, s: S, msg: &'static str) { - self.span_warn_with_code(s, msg, DiagnosticId::Error(String::from("Aeneas"))); - } -} - -pub fn span_err(sess: &Session, span: rustc_span::Span, msg: &'static str) { - log::error!("{}:\n{}", meta::span_to_string(sess, span), msg); - sess.span_err(span, msg); -} - -pub fn span_warn(sess: &Session, span: rustc_span::Span, msg: &'static str) { - log::warn!("{}:\n{}", meta::span_to_string(sess, span), msg); - sess.span_warn(span, msg); +/// Common error used during the translation. +#[derive(Debug)] +pub struct Error { + pub span: rustc_span::Span, + pub msg: String, } /// Custom function to pretty-print elements from an iterator @@ -118,15 +62,6 @@ pub fn write_vec( write_iterator(write_t, f, v.iter()) } -/// Assertion which doesn't panick -pub fn assert(x: bool) -> Result<()> { - if x { - Ok(()) - } else { - Err(()) - } -} - /// This macro computes the name of the function in which it is called. /// We adapted it from: /// @@ -158,126 +93,35 @@ macro_rules! function_name { /// A custom log trace macro. Uses the log crate. macro_rules! trace { ($($arg:tt)+) => {{ + use colored::Colorize; let msg = format!($($arg)+); - log::trace!("[{}]:\n{}", function_name!(), msg) + log::trace!("[{}]:\n{}", function_name!().yellow(), msg) }}; () => {{ - log::trace!("[{}]", function_name!()) + use colored::Colorize; + log::trace!("[{}]", function_name!().yellow()) }}; } /// A custom log error macro. Uses the log crate. macro_rules! error { ($($arg:tt)+) => {{ + use colored::Colorize; let msg = format!($($arg)+); - log::error!("[{}]:\n{}", function_name!(), msg) + log::error!("[{}]:\n{}", function_name!().red(), msg) }}; } /// A custom log info macro. Uses the log crate. macro_rules! info { ($($arg:tt)+) => {{ + use colored::Colorize; let msg = format!($($arg)+); // As for info we generally output simple messages, we don't insert // a breakline - log::info!("[{}]: {}", function_name!(), msg) + log::info!("[{}]: {}", function_name!().yellow(), msg) }}; () => {{ - log::info!("[{}]", function_name!()) + log::info!("[{}]", function_name!().yellow()) }}; } - -/// Serialize a vector -pub fn serialize_vec( - v: &Vec, - serializer: S, -) -> std::result::Result { - use serde::ser::SerializeSeq; - // Note that we don't write the sequence length in the json file: it causes - // errors with OCaml's Yojson (because then we mix named fields with unnamed - // fields). - let mut seq = serializer.serialize_seq(Some(v.len()))?; - for e in v { - seq.serialize_element(e)?; - } - seq.end() -} - -/// Serialize a collection by using an iterator on this collection -pub fn serialize_collection, S: Serializer>( - it: I, - serializer: S, -) -> std::result::Result { - // For simplicity, we convert to a vec (this gives us the length) - let v = Vec::from_iter(it); - serialize_vec(&v, serializer) -} - -pub fn serialize_vector( - v: &Vector, - serializer: S, -) -> std::result::Result { - serialize_collection(v.iter(), serializer) -} - -/// Wrapper to serialize std::vec::Vec -/// -/// We need this because serialization is implemented via the trait system. -pub struct VecSerializer<'a, T> { - pub vector: &'a Vec, -} - -impl<'a, T> VecSerializer<'a, T> { - pub fn new(vector: &'a Vec) -> Self { - VecSerializer { vector } - } -} - -impl<'a, T: Serialize> Serialize for VecSerializer<'a, T> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serialize_vec(self.vector, serializer) - } -} - -/// Wrapper to serialize vectors from im::Vector. -/// -/// We need this because serialization is implemented via the trait system. -pub struct VectorSerializer<'a, T: Clone> { - pub vector: &'a Vector, -} - -impl<'a, T: Clone> VectorSerializer<'a, T> { - pub fn new(vector: &'a Vector) -> Self { - VectorSerializer { vector } - } -} - -impl<'a, T: Clone + Serialize> Serialize for VectorSerializer<'a, T> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serialize_vector(self.vector, serializer) - } -} - -#[test] -fn test_propagate_error() { - let ints = &[1, 2, 3, 4, 5, 6]; - let mut sum = 0; - let res = propagate_error( - |f| ints.iter().for_each(f), - |x| { - if *x == 4 { - return Err(()); - } - sum += *x; - Ok(()) - }, - ); - assert!(res.is_err()); - assert!(sum == 6); -} diff --git a/charon/src/driver.rs b/charon/src/driver.rs index 0939ab58..d8504028 100644 --- a/charon/src/driver.rs +++ b/charon/src/driver.rs @@ -1,21 +1,17 @@ -#![allow(dead_code)] - use crate::cli_options; use crate::export; -use crate::extract_global_assignments; use crate::get_mir::MirLevel; use crate::index_to_function_calls; use crate::insert_assign_return_unit; -use crate::llbc_ast::{CtxNames, FunDeclId, GlobalDeclId}; use crate::ops_to_function_calls; use crate::reconstruct_asserts; -use crate::regions_hierarchy; -use crate::regularize_constant_adts; use crate::remove_drop_never; use crate::remove_dynamic_checks; +use crate::remove_nops; use crate::remove_read_discriminant; use crate::remove_unused_locals; use crate::reorder_decls; +use crate::simplify_constants; use crate::translate_crate_to_ullbc; use crate::translate_ctx; use crate::ullbc_to_llbc; @@ -31,6 +27,8 @@ use std::ops::Deref; /// The callbacks for Charon pub struct CharonCallbacks { pub options: cli_options::CliOpts, + /// This is to be filled during the extraction + pub error_count: usize, } impl Callbacks for CharonCallbacks { @@ -117,7 +115,8 @@ pub fn get_args_crate_index>(args: &[T]) -> Option /// Translate a crate to LLBC (Low-Level Borrow Calculus). /// /// This function is a callback function for the Rust compiler. -pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Result<(), ()> { +#[allow(clippy::result_unit_err)] +pub fn translate(sess: &Session, tcx: TyCtxt, internal: &mut CharonCallbacks) -> Result<(), ()> { trace!(); let options = &internal.options; @@ -156,18 +155,20 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res // # Translate the declarations in the crate. // We translate the declarations in an ad-hoc order, and do not group // the mutually recursive groups - we do this in the next step. - let mut ctx = translate_crate_to_ullbc::translate(crate_info, sess, tcx, mir_level); + let mut ctx = translate_crate_to_ullbc::translate(crate_info, options, sess, tcx, mir_level); + + trace!("# After translation from MIR:\n\n{}\n", ctx); + + if options.print_ullbc { + info!("# ULLBC after translation from MIR:\n\n{}\n", ctx); + } // # Reorder the graph of dependencies and compute the strictly // connex components to: // - compute the order in which to extract the definitions // - find the recursive definitions // - group the mutually recursive definitions - let ordered_decls = reorder_decls::reorder_declarations(&ctx)?; - - // # Compute the regions hierarchies for the types and the function signatures - // TODO: move to Aeneas - regions_hierarchy::compute(&mut ctx, &ordered_decls); + let ordered_decls = reorder_decls::reorder_declarations(&ctx); // // ================= @@ -177,31 +178,9 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res // we simply apply some micro-passes to make the code cleaner, before // serializing the result. - let type_defs = &mut ctx.type_defs; - let mut ullbc_funs = &mut ctx.fun_defs; - let mut ullbc_globals = &mut ctx.global_defs; - - // Compute the list of function and global names in the context. - // We need this for pretty-printing (i.e., debugging) purposes. - // We could use the [FunDecls] and [GlobalDecls] contexts, but we often - // mutably borrow them to modify them in place, which prevents us from - // using them for pretty-printing purposes (we would need to create shared - // borrows over already mutably borrowed values). - let fun_names: FunDeclId::Map = - FunDeclId::Map::from_iter(ullbc_funs.iter().map(|d| (d.def_id, d.name.to_string()))); - let global_names: GlobalDeclId::Map = - GlobalDeclId::Map::from_iter(ullbc_globals.iter().map(|d| (d.def_id, d.name.to_string()))); - let fmt_ctx = CtxNames::new(&type_defs, &fun_names, &global_names); - - // # Micro-pass: replace constant ([OperandConstantValue]) ADTs by regular - // (Aggregated) ADTs. - regularize_constant_adts::transform(&fmt_ctx, &mut ullbc_funs, &mut ullbc_globals); - - // # Micro-pass: extract statics and constant globals from operands (put them in - // a let binding). This pass relies on the absence of constant ADTs from - // the previous step: it does not inspect them (and would thus miss globals - // in constant ADTs). - extract_global_assignments::transform(&fmt_ctx, &mut ullbc_funs, &mut ullbc_globals); + // # Micro-pass: desugar the constants to other values/operands as much + // as possible. + simplify_constants::transform(&mut ctx); // # There are two options: // - either the user wants the unstructured LLBC, in which case we stop there @@ -211,23 +190,29 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res if options.ullbc { // # Extract the files export::export_ullbc( + &ctx, crate_name, - &ctx.id_to_file, &ordered_decls, - &type_defs, - &ullbc_funs, - &ullbc_globals, + &ctx.fun_defs, + &ctx.global_defs, &options.dest_dir, )?; } else { // # Go from ULLBC to LLBC (Low-Level Borrow Calculus) by reconstructing // the control flow. - let (mut llbc_funs, mut llbc_globals) = ullbc_to_llbc::translate_functions( - options.no_code_duplication, - &type_defs, - &ullbc_funs, - &ullbc_globals, - ); + let (mut llbc_funs, mut llbc_globals) = ullbc_to_llbc::translate_functions(&ctx); + + if options.print_built_llbc { + let llbc_ctx = crate::translate_ctx::LlbcTransCtx { + ctx: &ctx, + llbc_globals: &llbc_globals, + llbc_funs: &llbc_funs, + }; + info!( + "# LLBC resulting from control-flow reconstruction:\n\n{}\n", + llbc_ctx + ); + } // # Micro-pass: remove the dynamic checks for array/slice bounds // and division by zero. @@ -235,30 +220,31 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res // introduced by Rustc use a special "assert" construct. Because of // this, it must happen *before* the [reconstruct_asserts] pass. // See the comments in [crate::remove_dynamic_checks]. - remove_dynamic_checks::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + remove_dynamic_checks::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: reconstruct the asserts - reconstruct_asserts::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + reconstruct_asserts::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // TODO: we should mostly use the TransCtx to format declarations + use crate::formatter::Formatter; for (_, def) in &llbc_funs { trace!( "# After asserts reconstruction:\n{}\n", - def.fmt_with_decls(&type_defs, &llbc_funs, &llbc_globals) + ctx.format_object(def) ); } - // # Micro-pass: replace some unops/binops with function calls - // (introduces: ArrayToSlice, etc.) - ops_to_function_calls::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + // # Micro-pass: replace some unops/binops and the array aggregates with + // function calls (introduces: ArrayToSlice, etc.) + ops_to_function_calls::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: replace the arrays/slices index operations with function // calls. // (introduces: ArrayIndexShared, ArrayIndexMut, etc.) - index_to_function_calls::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + index_to_function_calls::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: Remove the discriminant reads (merge them with the switches) - remove_read_discriminant::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + remove_read_discriminant::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: add the missing assignments to the return value. // When the function return type is unit, the generated MIR doesn't @@ -268,30 +254,40 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res // an extra assignment just before returning. // This also applies to globals (for checking or executing code before // the main or at compile-time). - insert_assign_return_unit::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + insert_assign_return_unit::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: remove the drops of locals whose type is `Never` (`!`). This // is in preparation of the next transformation. - remove_drop_never::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + remove_drop_never::transform(&ctx, &mut llbc_funs, &mut llbc_globals); // # Micro-pass: remove the locals which are never used. After doing so, we // check that there are no remaining locals with type `Never`. - remove_unused_locals::transform(&fmt_ctx, &mut llbc_funs, &mut llbc_globals); + remove_unused_locals::transform(&ctx, &mut llbc_funs, &mut llbc_globals); + + // # Micro-pass (not necessary, but good for cleaning): remove the + // useless no-ops. + remove_nops::transform(&ctx, &mut llbc_funs, &mut llbc_globals); trace!("# Final LLBC:\n"); for (_, def) in &llbc_funs { - trace!( - "#{}\n", - def.fmt_with_decls(&type_defs, &llbc_funs, &llbc_globals) - ); + trace!("#{}\n", ctx.format_object(def)); + } + + let llbc_ctx = crate::translate_ctx::LlbcTransCtx { + ctx: &ctx, + llbc_globals: &llbc_globals, + llbc_funs: &llbc_funs, + }; + trace!("# About to export:\n\n{}\n", llbc_ctx); + if options.print_llbc { + info!("# Final LLBC before serialization:\n\n{}\n", llbc_ctx); } // # Final step: generate the files. export::export_llbc( + &ctx, crate_name, - &ctx.id_to_file, &ordered_decls, - &type_defs, &llbc_funs, &llbc_globals, &options.dest_dir, @@ -299,5 +295,8 @@ pub fn translate(sess: &Session, tcx: TyCtxt, internal: &CharonCallbacks) -> Res } trace!("Done"); + // Update the error count + internal.error_count = ctx.error_count; + Ok(()) } diff --git a/charon/src/export.rs b/charon/src/export.rs index caaa1d14..63fb3be7 100644 --- a/charon/src/export.rs +++ b/charon/src/export.rs @@ -1,70 +1,44 @@ -use crate::common::*; use crate::llbc_ast; use crate::meta::{FileId, FileName}; -use crate::reorder_decls::DeclarationsGroups; +use crate::reorder_decls::{DeclarationGroup, DeclarationsGroups}; +use crate::translate_ctx::*; use crate::types::*; use crate::ullbc_ast; -use crate::ullbc_ast::{FunDeclId, GlobalDeclId}; -use serde::{Serialize, Serializer}; -use std::collections::HashMap; +use crate::ullbc_ast::{FunDeclId, GlobalDeclId, TraitDecl, TraitImpl}; +use serde::Serialize; use std::fs::File; use std::path::PathBuf; -/// Serialization wrapper for vectors -pub struct VecSW<'a, T> { - pub vector: &'a Vec, -} - -impl<'a, T> VecSW<'a, T> { - pub fn new(vector: &'a Vec) -> Self { - VecSW { vector } - } -} - -impl<'a, T: Serialize> Serialize for VecSW<'a, T> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serialize_vec(self.vector, serializer) - } -} - -// TODO: there is an equivalent definition somewhere -type DeclarationGroup = - crate::reorder_decls::DeclarationGroup; - -/// An auxiliary type used for serialization of declaration groups -type DeclarationsSerializer<'a> = VecSW<'a, DeclarationGroup>; - /// A generic crate, which implements the [Serialize] trait #[derive(Serialize)] #[serde(rename = "Crate")] -struct GCrateSerializer<'a, FD: Serialize + Clone, GD: Serialize + Clone> { +struct GCrateSerializer<'a, FD, GD> { name: String, /// The `id_to_file` map is serialized as a vector. /// We use this map for the spans: the spans only store the file ids, not /// the file names, in order to save space. - id_to_file: VecSW<'a, (FileId::Id, FileName)>, - declarations: DeclarationsSerializer<'a>, - types: VecSW<'a, TypeDecl>, - functions: VecSW<'a, FD>, - globals: VecSW<'a, GD>, + id_to_file: &'a Vec<(FileId::Id, FileName)>, + declarations: &'a Vec, + types: Vec, + functions: Vec, + globals: Vec, + trait_decls: Vec, + trait_impls: Vec, } /// Export the translated definitions to a JSON file. /// /// This is a generic function, used both for LLBC and ULLBC. +#[allow(clippy::result_unit_err)] pub fn gexport( + ctx: &TransCtx, crate_name: String, - id_to_file: &HashMap, ordered_decls: &DeclarationsGroups, - type_defs: &TypeDecls, fun_defs: &FunDeclId::Map, global_defs: &GlobalDeclId::Map, dest_dir: &Option, extension: &str, -) -> Result<()> { +) -> Result<(), ()> { // Generate the destination file - we use the crate name for the file name let mut target_filename = dest_dir .as_deref() @@ -75,27 +49,32 @@ pub fn gexport( // Transform the map file id -> file into a vector. // Sort the vector to make the serialized file as stable as possible. + let id_to_file = &ctx.id_to_file; let mut file_ids: Vec = id_to_file.keys().copied().collect(); file_ids.sort(); let id_to_file: Vec<(FileId::Id, FileName)> = file_ids .into_iter() .map(|id| (id, id_to_file.get(&id).unwrap().clone())) .collect(); - let id_to_file = VecSW::new(&id_to_file); + let id_to_file = &id_to_file; // Serialize // Note that we replace the maps with vectors (the declarations contain // their ids, so it is easy to reconstruct the maps from there). - let types = type_defs.iter().cloned().collect(); - let funs = fun_defs.iter().cloned().collect(); + let types = ctx.type_defs.iter().cloned().collect(); + let functions = fun_defs.iter().cloned().collect(); let globals = global_defs.iter().cloned().collect(); + let trait_decls = ctx.trait_decls.iter().cloned().collect(); + let trait_impls = ctx.trait_impls.iter().cloned().collect(); let crate_serializer = GCrateSerializer { name: crate_name, id_to_file, - declarations: VecSW::new(ordered_decls), - types: VecSW::new(&types), - functions: VecSW::new(&funs), - globals: VecSW::new(&globals), + declarations: ordered_decls, + types, + functions, + globals, + trait_decls, + trait_impls, }; // Create the directory, if necessary (note that if the target directory @@ -119,7 +98,14 @@ pub fn gexport( // We canonicalize (i.e., make absolute) the path before printing it: // this makes it clearer to the user where to find the file. let path = std::fs::canonicalize(target_filename).unwrap(); - info!("Generated the file: {}", path.to_str().unwrap()); + if ctx.error_count > 0 { + info!( + "Generated the partial (because we encountered errors) file: {}", + path.to_str().unwrap() + ); + } else { + info!("Generated the file: {}", path.to_str().unwrap()); + } Ok(()) } std::result::Result::Err(_) => { @@ -135,20 +121,19 @@ pub fn gexport( } /// Export the translated ULLBC definitions to a JSON file. +#[allow(clippy::result_unit_err)] pub fn export_ullbc( + ctx: &TransCtx, crate_name: String, - id_to_file: &HashMap, ordered_decls: &DeclarationsGroups, - type_defs: &TypeDecls, fun_defs: &ullbc_ast::FunDecls, global_defs: &ullbc_ast::GlobalDecls, dest_dir: &Option, -) -> Result<()> { +) -> Result<(), ()> { gexport( + ctx, crate_name, - id_to_file, ordered_decls, - type_defs, fun_defs, global_defs, dest_dir, @@ -157,20 +142,19 @@ pub fn export_ullbc( } /// Export the translated LLBC definitions to a JSON file. +#[allow(clippy::result_unit_err)] pub fn export_llbc( + ctx: &TransCtx, crate_name: String, - id_to_file: &HashMap, ordered_decls: &DeclarationsGroups, - type_defs: &TypeDecls, fun_defs: &llbc_ast::FunDecls, global_defs: &llbc_ast::GlobalDecls, dest_dir: &Option, -) -> Result<()> { +) -> Result<(), ()> { gexport( + ctx, crate_name, - id_to_file, ordered_decls, - type_defs, fun_defs, global_defs, dest_dir, diff --git a/charon/src/expressions.rs b/charon/src/expressions.rs index 830ff40e..3c17641f 100644 --- a/charon/src/expressions.rs +++ b/charon/src/expressions.rs @@ -1,7 +1,9 @@ //! Implements expressions: paths, operands, rvalues, lvalues pub use crate::expressions_utils::*; +use crate::gast::{FunDeclId, TraitItemName}; use crate::types::*; +pub use crate::values::VarId; use crate::values::*; use macros::{EnumAsGetters, EnumIsA, EnumToGetters, VariantIndexArity, VariantName}; use serde::Serialize; @@ -9,6 +11,7 @@ use std::vec::Vec; #[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct Place { + // TODO: update to transform to a recursive type pub var_id: VarId::Id, pub projection: Projection, } @@ -37,12 +40,10 @@ pub enum ProjectionElem { /// In rust, this comes from the `*` operator applied on boxes. DerefBox, /// Dereference a raw pointer. See the comments for [crate::types::Ty::RawPtr]. - /// TODO: remove those? Or if we keep them, change to: `Deref(DerefKind)`? + /// TODO: remove those (we would also need: `DerefPtrUnique`, `DerefPtrNonNull`, etc.) + /// and only keep a single `Deref` variant? + /// Or if we keep them, change to: `Deref(DerefKind)`? DerefRawPtr, - /// Dereference a unique pointer. See the comments for [crate::types::Ty::RawPtr]. - DerefPtrUnique, - /// Dereference a non-null pointer. See the comments for [crate::types::Ty::RawPtr]. - DerefPtrNonNull, /// Projection from ADTs (variants, structures). /// We allow projections to be used as left-values and right-values. /// We should never have projections to fields of symbolic variants (they @@ -57,16 +58,13 @@ pub enum ProjectionElem { /// We also keep the type of the array/slice that we index for convenience purposes /// (this is not necessary). /// We **eliminate** this variant in a micro-pass. - Index(VarId::Id, ETy), + Index(VarId::Id, Ty), } #[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIsA, EnumAsGetters, Serialize)] pub enum FieldProjKind { #[serde(rename = "ProjAdt")] Adt(TypeDeclId::Id, Option), - /// The option type is assumed (it comes from the standard library) - #[serde(rename = "ProjOption")] - Option(VariantId::Id), /// If we project from a tuple, the projection kind gives the arity of the #[serde(rename = "ProjTuple")] Tuple(usize), @@ -95,13 +93,8 @@ pub enum UnOp { /// (in debug mode) to check that it is not equal to the minimum integer /// value (for the proper type). Neg, - /// Casts are rvalues in MIR, but we treat them as unops. For now, we - /// only support for integer to integer, but we can also do from integers/booleans - /// to integers/booleans. For now, we don't handle pointer casts. - /// - /// The first integer type gives the source type, the second one gives - /// the destination type. - Cast(IntegerTy, IntegerTy), + /// Casts are rvalues in MIR, but we treat them as unops. + Cast(CastKind), /// Coercion from array (i.e., [T; N]) to slice. /// /// **Remark:** We introduce this unop when translating from MIR, **then transform** @@ -109,7 +102,15 @@ pub enum UnOp { /// *necessary* as we can retrieve them from the context, but storing them here is /// very useful. The [RefKind] argument states whethere we operate on a mutable /// or a shared borrow to an array. - ArrayToSlice(RefKind, ETy, ConstGeneric), + ArrayToSlice(RefKind, Ty, ConstGeneric), +} + +/// For all the variants: the first type gives the source type, the second one gives +/// the destination type. +#[derive(Debug, PartialEq, Eq, Clone, EnumIsA, VariantName, Serialize)] +pub enum CastKind { + Integer(IntegerTy, IntegerTy), + FnPtr(Ty, Ty), } /// Binary operations. @@ -148,15 +149,102 @@ pub enum Operand { Copy(Place), Move(Place), /// Constant value (including constant and static variables) - Const(ETy, OperandConstantValue), + Const(ConstantExpr), +} + +/// A function identifier. See [crate::ullbc_ast::Terminator] +#[derive(Debug, Clone, PartialEq, Eq, EnumIsA, EnumAsGetters, VariantName, Serialize)] +pub enum FunId { + /// A "regular" function (function local to the crate, external function + /// not treated as a primitive one). + Regular(FunDeclId::Id), + /// A primitive function, coming from a standard library (for instance: + /// `alloc::boxed::Box::new`). + /// TODO: rename to "Primitive" + Assumed(AssumedFunId), +} + +/// An assumed function identifier, identifying a function coming from a +/// standard library. +#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIsA, EnumAsGetters, VariantName, Serialize)] +pub enum AssumedFunId { + /// `alloc::boxed::Box::new` + BoxNew, + /// `alloc::alloc::box_free` + /// This is actually an unsafe function, but the rust compiler sometimes + /// introduces it when going to MIR. + /// + /// Also, in practice, deallocation is performed as follows in MIR: + /// ```text + /// alloc::alloc::box_free::( + /// move (b.0: std::ptr::Unique), + /// move (b.1: std::alloc::Global)) + /// ``` + /// When translating from MIR to ULLBC, we do as if the MIR was actually the + /// following (this is hardcoded - see [crate::register] and [crate::translate_functions_to_ullbc]): + /// ```text + /// alloc::alloc::box_free::(move b) + /// ``` + /// + /// Also see the comments in [crate::assumed::type_to_used_params]. + BoxFree, + /// Converted from [ProjectionElem::Index]. + /// + /// Signature: `fn(&[T;N], usize) -> &T` + ArrayIndexShared, + /// Converted from [ProjectionElem::Index]. + /// + /// Signature: `fn(&mut [T;N], usize) -> &mut T` + ArrayIndexMut, + /// Cast an array as a slice. + /// + /// Converted from [UnOp::ArrayToSlice] + ArrayToSliceShared, + /// Cast an array as a slice. + /// + /// Converted from [UnOp::ArrayToSlice] + ArrayToSliceMut, + /// `repeat(n, x)` returns an array where `x` has been replicated `n` times. + /// + /// We introduce this when desugaring the [ArrayRepeat] rvalue. + ArrayRepeat, + /// Converted from [ProjectionElem::Index]. + /// + /// Signature: `fn(&[T], usize) -> &T` + SliceIndexShared, + /// Converted from [ProjectionElem::Index]. + /// + /// Signature: `fn(&mut [T], usize) -> &mut T` + SliceIndexMut, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, EnumAsGetters)] +pub enum FunIdOrTraitMethodRef { + Fun(FunId), + /// If a trait: the reference to the trait and the id of the trait method. + /// The fun decl id is not really necessary - we put it here for convenience + /// purposes. + Trait(TraitRef, TraitItemName, FunDeclId::Id), } -/// Constant value for an operand. -/// Only the `ConstantValue` case is remaining in LLBC final form. +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +pub struct FnPtr { + pub func: FunIdOrTraitMethodRef, + pub generics: GenericArgs, + /// If this is a reference to a trait method: stores *all* the generic arguments + /// which apply to the trait + the method. The fields [region_args], [type_args] + /// [const_generic_args] only store the arguments which concern the method call. + /// See the comments for [ParamsInfo]. + pub trait_and_method_generic_args: Option, +} + +/// A constant expression. +/// +/// Only the [Literal] and [Var] cases are left in the final LLBC. /// /// The other cases come from a straight translation from the MIR: /// -/// `Adt` case: +/// [Adt] case: /// It is a bit annoying, but rustc treats some ADT and tuple instances as /// constants when generating MIR: /// - an enumeration with one variant and no fields is a constant. @@ -165,11 +253,19 @@ pub enum Operand { /// (if all the fields are constant) rather than as an aggregated value /// We later desugar those to regular ADTs, see [regularize_constant_adts.rs]. /// -/// `Identifier` and `Static` case: -/// Match constant variables. We later desugar those to separate statements, -/// see [extract_global_assignments.rs]. -#[derive(Debug, PartialEq, Eq, Clone, VariantName, EnumIsA, EnumAsGetters, VariantIndexArity)] -pub enum OperandConstantValue { +/// [Global] case: access to a global variable. We later desugar it to +/// a separate statement. +/// +/// [Ref] case: reference to a constant value. We later desugar it to a separate +/// statement. +/// +/// [FnPtr] case: a function pointer (to a top-level function). +/// +/// Remark: +/// MIR seems to forbid more complex expressions like paths. For instance, +/// reading the constant `a.b` is translated to `{ _1 = const a; _2 = (_1.0) }`. +#[derive(Debug, PartialEq, Eq, Clone, Serialize, VariantName, EnumIsA, EnumAsGetters)] +pub enum RawConstantExpr { Literal(Literal), /// /// In most situations: @@ -177,17 +273,44 @@ pub enum OperandConstantValue { /// no fields, unit (encoded as a 0-tuple). /// /// Less frequently: arbitrary ADT values. - Adt(Option, Vec), /// - /// The case when the constant is elsewhere. - /// The MIR seems to forbid more complex expressions like paths : - /// Reading the constant a.b is translated to { _1 = const a; _2 = (_1.0) }. - ConstantId(GlobalDeclId::Id), + /// We eliminate this case in a micro-pass. + Adt(Option, Vec), + /// + /// The value is a top-level value. + /// + /// We eliminate this case in a micro-pass. + Global(GlobalDeclId::Id), + /// + /// A trait constant. /// - /// Same as for constants, except that statics are accessed through references. - StaticId(GlobalDeclId::Id), + /// Ex.: + /// ```text + /// impl Foo for Bar { + /// const C : usize = 32; // <- + /// } + /// ``` + /// + /// Remark: in the generic args, the trait refs are necessarily empty. + /// + /// Remark: trait constants can not be used in types, they are necessarily + /// values. For this reason, we can always erase the regions. + TraitConst(TraitRef, GenericArgs, TraitItemName), + /// + /// A shared reference to a constant value + /// + /// We eliminate this case in a micro-pass. + Ref(Box), /// A const generic var Var(ConstGenericVarId::Id), + /// Function pointer + FnPtr(FnPtr), +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +pub struct ConstantExpr { + pub value: RawConstantExpr, + pub ty: Ty, } /// TODO: we could factor out [Rvalue] and function calls (for LLBC, not ULLBC). @@ -236,26 +359,20 @@ pub enum Rvalue { /// together with the bounds checks**. Whenever the user writes `x.len()` /// where `x` is a slice or an array, they actually call a non-primitive /// function. - Len(Place, ETy, Option), + Len(Place, Ty, Option), + /// [Repeat(x, n)] creates an array where [x] is copied [n] times. + /// + /// We desugar this to a function call. + Repeat(Operand, Ty, ConstGeneric), } #[derive(Debug, Clone, VariantIndexArity, Serialize)] pub enum AggregateKind { - Tuple, - // TODO: treat Option in a general manner by merging it with the Adt case (we should - // extract the definitions of the external enumerations - because as they are public, - // their variants are public) - Option(VariantId::Id, ETy), - // TODO: do we really need this? - Range(ETy), - Adt( - TypeDeclId::Id, - Option, - Vec, - Vec, - Vec, - ), - // We don't put this with the ADT cas because this is the only assumed type - // with aggregates. - Array(ETy, ConstGeneric), + Adt(TypeId, Option, GenericArgs), + /// We don't put this with the ADT cas because this is the only assumed type + /// with aggregates, and it is a primitive type. In particular, it makes + /// sense to treat it differently because it has a variable number of fields. + Array(Ty, ConstGeneric), + /// + Closure(FunDeclId::Id), } diff --git a/charon/src/expressions_utils.rs b/charon/src/expressions_utils.rs index 381e9b53..4575a1c2 100644 --- a/charon/src/expressions_utils.rs +++ b/charon/src/expressions_utils.rs @@ -1,18 +1,19 @@ //! This file groups everything which is linked to implementations about [crate::expressions] -#![allow(dead_code)] - -use crate::assumed; use crate::expressions::*; use crate::formatter::Formatter; -use crate::gast::{AssumedFunId, Call, FunDeclId, FunId}; +use crate::gast::{AssumedFunId, Call, FunDeclId, FunId, FunIdOrTraitMethodRef, TraitItemName}; use crate::types::*; use crate::ullbc_ast::GlobalDeclId; use crate::values; use crate::values::*; use macros::make_generic_in_borrows; -use serde::{Serialize, Serializer}; use std::vec::Vec; +pub trait ExprFormatter = TypeFormatter + + Formatter + + Formatter<(TypeDeclId::Id, VariantId::Id)> + + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>; + impl Place { pub fn new(var_id: VarId::Id) -> Place { Place { @@ -33,13 +34,30 @@ impl std::fmt::Display for BorrowKind { } } -impl std::fmt::Display for UnOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { +impl CastKind { + pub fn fmt_with_ctx(&self, ctx: &T) -> String + where + T: TypeFormatter, + { + match self { + CastKind::Integer(src, tgt) => format!("cast<{src},{tgt}>"), + CastKind::FnPtr(src, tgt) => { + format!("cast<{},{}>", src.fmt_with_ctx(ctx), tgt.fmt_with_ctx(ctx)) + } + } + } +} + +impl UnOp { + pub fn fmt_with_ctx(&self, ctx: &T) -> String + where + T: TypeFormatter, + { match self { - UnOp::Not => write!(f, "~"), - UnOp::Neg => write!(f, "-"), - UnOp::Cast(src, tgt) => write!(f, "cast<{src},{tgt}>"), - UnOp::ArrayToSlice(..) => write!(f, "array_to_slice"), + UnOp::Not => "~".to_string(), + UnOp::Neg => "-".to_string(), + UnOp::Cast(kind) => kind.fmt_with_ctx(ctx), + UnOp::ArrayToSlice(..) => "array_to_slice".to_string(), } } } @@ -88,12 +106,6 @@ impl Place { ProjectionElem::DerefRawPtr => { out = format!("deref_raw_ptr ({out})"); } - ProjectionElem::DerefPtrUnique => { - out = format!("deref_ptr_unique ({out})"); - } - ProjectionElem::DerefPtrNonNull => { - out = format!("deref_ptr_non_null ({out})"); - } ProjectionElem::Field(proj_kind, field_id) => match proj_kind { FieldProjKind::Adt(adt_id, opt_variant_id) => { let field_name = ctx.format_object((*adt_id, *opt_variant_id, *field_id)); @@ -106,9 +118,6 @@ impl Place { FieldProjKind::Tuple(_) => { out = format!("({out}).{field_id}"); } - FieldProjKind::Option(_) => { - out = format!("({out}).{field_id}"); - } }, ProjectionElem::Index(i, _) => out = format!("({out})[{}]", ctx.format_object(*i)), } @@ -118,7 +127,7 @@ impl Place { } /// Perform a type substitution - actually simply clone the object - pub fn substitute(&self, _subst: &ETypeSubst) -> Self { + pub fn substitute(&self, _subst: &TypeSubst) -> Self { self.clone() } } @@ -129,16 +138,23 @@ impl std::fmt::Display for Place { } } -impl OperandConstantValue { +impl ConstantExpr { pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter, + T: TypeFormatter, + { + self.value.fmt_with_ctx(ctx) + } +} + +impl RawConstantExpr { + pub fn fmt_with_ctx(&self, ctx: &T) -> String + where + T: TypeFormatter, { match self { - OperandConstantValue::Literal(c) => c.to_string(), - OperandConstantValue::Adt(variant_id, values) => { + RawConstantExpr::Literal(c) => c.to_string(), + RawConstantExpr::Adt(variant_id, values) => { // It is a bit annoying: in order to properly format the value, // we need the type (which contains the type def id). // Anyway, the printing utilities are mostly for debugging. @@ -149,14 +165,48 @@ impl OperandConstantValue { let values: Vec = values.iter().map(|v| v.fmt_with_ctx(ctx)).collect(); format!("ConstAdt {} [{}]", variant_id, values.join(", ")) } - OperandConstantValue::ConstantId(id) => ctx.format_object(*id), - OperandConstantValue::StaticId(id) => format!("alloc: &{}", ctx.format_object(*id)), - OperandConstantValue::Var(id) => format!("const {}", ctx.format_object(*id)), + RawConstantExpr::Global(id) => ctx.format_object(*id), + RawConstantExpr::TraitConst(trait_ref, substs, name) => { + format!( + "{}{}::{name}", + trait_ref.fmt_with_ctx(ctx), + substs.fmt_with_ctx_split_trait_refs(ctx) + ) + } + RawConstantExpr::Ref(cv) => { + format!("&{}", cv.fmt_with_ctx(ctx)) + } + RawConstantExpr::Var(id) => format!("const {}", ctx.format_object(*id)), + RawConstantExpr::FnPtr(f) => { + format!("{}", f.fmt_with_ctx(ctx),) + } } } } -impl std::fmt::Display for OperandConstantValue { +impl FnPtr { + pub fn fmt_with_ctx(&self, ctx: &T) -> String + where + T: TypeFormatter, + { + let generics = self.generics.fmt_with_ctx_split_trait_refs(ctx); + let f = match &self.func { + FunIdOrTraitMethodRef::Fun(FunId::Regular(def_id)) => { + format!("{}", ctx.format_object(*def_id),) + } + FunIdOrTraitMethodRef::Fun(FunId::Assumed(assumed)) => { + format!("@{}", assumed.variant_name()) + } + FunIdOrTraitMethodRef::Trait(trait_ref, method_id, _) => { + format!("{}::{}", trait_ref.fmt_with_ctx(ctx), &method_id.0) + } + }; + + format!("{}{}", f, generics) + } +} + +impl std::fmt::Display for ConstantExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", self.fmt_with_ctx(&values::DummyFormatter {})) } @@ -165,21 +215,17 @@ impl std::fmt::Display for OperandConstantValue { impl Operand { pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + T: ExprFormatter, { match self { Operand::Copy(p) => format!("copy ({})", p.fmt_with_ctx(ctx)), Operand::Move(p) => format!("move ({})", p.fmt_with_ctx(ctx)), - Operand::Const(_, c) => format!("const ({})", c.fmt_with_ctx(ctx)), + Operand::Const(c) => format!("const ({})", c.fmt_with_ctx(ctx)), } } /// Perform a type substitution - actually simply clone the object - pub fn substitute(&self, _subst: &ETypeSubst) -> Self { + pub fn substitute(&self, _subst: &TypeSubst) -> Self { self.clone() } } @@ -191,16 +237,9 @@ impl std::fmt::Display for Operand { } impl Rvalue { - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - + Formatter - + Formatter - + Formatter<&'a ErasedRegion>, + T: ExprFormatter, { match self { Rvalue::Use(x) => x.fmt_with_ctx(ctx), @@ -213,7 +252,7 @@ impl Rvalue { BorrowKind::Shallow => format!("&shallow {}", place.fmt_with_ctx(ctx)), }, Rvalue::UnaryOp(unop, x) => { - format!("{}({})", unop, x.fmt_with_ctx(ctx)) + format!("{}({})", unop.fmt_with_ctx(ctx), x.fmt_with_ctx(ctx)) } Rvalue::BinaryOp(binop, x, y) => { format!("{} {} {}", x.fmt_with_ctx(ctx), binop, y.fmt_with_ctx(ctx)) @@ -224,48 +263,50 @@ impl Rvalue { Rvalue::Aggregate(kind, ops) => { let ops_s: Vec = ops.iter().map(|op| op.fmt_with_ctx(ctx)).collect(); match kind { - AggregateKind::Tuple => format!("({})", ops_s.join(", ")), - AggregateKind::Option(variant_id, _) => { - if *variant_id == assumed::OPTION_NONE_VARIANT_ID { - assert!(ops.is_empty()); - "@Option::None".to_string() - } else if *variant_id == assumed::OPTION_SOME_VARIANT_ID { - assert!(ops.len() == 1); - format!("@Option::Some({})", ops[0].fmt_with_ctx(ctx)) - } else { - unreachable!(); + AggregateKind::Adt(def_id, variant_id, _) => { + match def_id { + TypeId::Tuple => format!("({})", ops_s.join(", ")), + TypeId::Assumed(_) => unreachable!(), + TypeId::Adt(def_id) => { + // Format every field + let mut fields = vec![]; + for (i, op) in ops.iter().enumerate() { + let field_id = FieldId::Id::new(i); + let field_name = + ctx.format_object((*def_id, *variant_id, field_id)); + fields.push(format!( + "{}: {}", + field_name, + op.fmt_with_ctx(ctx) + )); + } + + let variant = match variant_id { + None => ctx.format_object(*def_id), + Some(variant_id) => ctx.format_object((*def_id, *variant_id)), + }; + format!("{} {{ {} }}", variant, fields.join(", ")) + } } } - AggregateKind::Adt(def_id, variant_id, _, _, _) => { - // Format every field - let mut fields = vec![]; - for (i, op) in ops.iter().enumerate() { - let field_id = FieldId::Id::new(i); - let field_name = ctx.format_object((*def_id, *variant_id, field_id)); - fields.push(format!("{}: {}", field_name, op.fmt_with_ctx(ctx))); - } - - let variant = match variant_id { - None => ctx.format_object(*def_id), - Some(variant_id) => ctx.format_object((*def_id, *variant_id)), - }; - format!("{} {{ {} }}", variant, fields.join(", ")) + AggregateKind::Array(_, len) => { + format!("[{}; {}]", ops_s.join(", "), len.fmt_with_ctx(ctx)) } - AggregateKind::Array(_, _) => { - format!("[{}]", ops_s.join(", ")) - } - AggregateKind::Range(_) => { - format!("@Range[{}]", ops_s.join(", ")) + AggregateKind::Closure(fn_id) => { + format!("{}", ctx.format_object(*fn_id)) } } } Rvalue::Global(gid) => ctx.format_object(*gid), Rvalue::Len(place, ..) => format!("len({})", place.fmt_with_ctx(ctx)), + Rvalue::Repeat(op, _ty, cg) => { + format!("[{}; {}]", op.fmt_with_ctx(ctx), cg.fmt_with_ctx(ctx)) + } } } /// Perform a type substitution - actually simply clone the object - pub fn substitute(&self, _subst: &ETypeSubst) -> Self { + pub fn substitute(&self, _subst: &TypeSubst) -> Self { self.clone() } } @@ -276,20 +317,6 @@ impl std::fmt::Display for Rvalue { } } -impl Serialize for OperandConstantValue { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - match self { - // [OperandConstantValue] exists only to handle temporary cases inherited from the MIR: - // for the final (U)LLBC format, we simply export the underlying constant value. - OperandConstantValue::Literal(cv) => cv.serialize(serializer), - _ => unreachable!("unexpected `{:?}`: `OperandConstantValue` fields other than `ConstantValue` are temporary and should not occur in serialized LLBC", self), - } - } -} - // Derive two implementations at once: one which uses shared borrows, and one // which uses mutable borrows. // Generates the traits: `SharedExprVisitor` and `MutExprVisitor`. @@ -317,8 +344,6 @@ pub trait ExprVisitor: crate::types::TypeVisitor { ProjectionElem::Deref => self.visit_deref(), ProjectionElem::DerefBox => self.visit_deref_box(), ProjectionElem::DerefRawPtr => self.visit_deref_raw_ptr(), - ProjectionElem::DerefPtrUnique => self.visit_deref_ptr_unique(), - ProjectionElem::DerefPtrNonNull => self.visit_deref_ptr_non_null(), ProjectionElem::Field(proj_kind, fid) => self.visit_projection_field(proj_kind, fid), ProjectionElem::Index(i, _) => self.visit_var_id(i), } @@ -331,15 +356,13 @@ pub trait ExprVisitor: crate::types::TypeVisitor { fn visit_deref(&mut self) {} fn visit_deref_box(&mut self) {} fn visit_deref_raw_ptr(&mut self) {} - fn visit_deref_ptr_unique(&mut self) {} - fn visit_deref_ptr_non_null(&mut self) {} fn visit_projection_field(&mut self, _: &FieldProjKind, _: &FieldId::Id) {} fn default_visit_operand(&mut self, o: &Operand) { match o { Operand::Copy(p) => self.visit_copy(p), Operand::Move(p) => self.visit_move(p), - Operand::Const(ety, cv) => self.visit_operand_const(ety, cv), + Operand::Const(cv) => self.visit_operand_const(cv), } } @@ -355,29 +378,40 @@ pub trait ExprVisitor: crate::types::TypeVisitor { self.visit_place(p) } - fn visit_operand_const(&mut self, ty: &ETy, op: &OperandConstantValue) { - self.visit_ty(ty); - self.visit_operand_constant_value(op); + fn visit_operand_const(&mut self, op: &ConstantExpr) { + self.visit_constant_expr(op); + } + + fn visit_constant_expr(&mut self, expr: &ConstantExpr) { + self.visit_ty(&expr.ty); + self.visit_raw_constant_expr(&expr.value); + } + + fn visit_raw_constant_expr(&mut self, expr: &RawConstantExpr) { + self.default_visit_raw_constant_expr(expr) } - fn visit_operand_constant_value(&mut self, op: &OperandConstantValue) { - use OperandConstantValue::*; - match op { + fn default_visit_raw_constant_expr(&mut self, expr: &RawConstantExpr) { + use RawConstantExpr::*; + match expr { Literal(lit) => self.visit_literal(lit), - Adt(oid, ops) => self.visit_operand_const_adt(oid, ops), - ConstantId(id) => self.visit_global_decl_id(id), - StaticId(id) => self.visit_global_decl_id(id), + Adt(oid, ops) => self.visit_constant_expr_adt(oid, ops), + Global(id) => self.visit_global_decl_id(id), + TraitConst(trait_ref, generics, _name) => { + self.visit_trait_ref(trait_ref); + self.visit_generic_args(generics); + } + Ref(cv) => self.visit_constant_expr(cv), Var(id) => self.visit_const_generic_var_id(id), + FnPtr(f) => { + self.visit_fn_ptr(f); + } } } - fn visit_operand_const_adt( - &mut self, - _oid: &Option, - ops: &Vec, - ) { + fn visit_constant_expr_adt(&mut self, _oid: &Option, ops: &Vec) { for op in ops { - self.visit_operand_constant_value(op) + self.visit_constant_expr(op) } } @@ -391,6 +425,7 @@ pub trait ExprVisitor: crate::types::TypeVisitor { Rvalue::Aggregate(kind, ops) => self.visit_aggregate(kind, ops), Rvalue::Global(gid) => self.visit_global(gid), Rvalue::Len(p, ty, cg) => self.visit_len(p, ty, cg), + Rvalue::Repeat(op, ty, cg) => self.visit_repeat(op, ty, cg), } } @@ -406,7 +441,18 @@ pub trait ExprVisitor: crate::types::TypeVisitor { self.visit_place(p) } - fn visit_unary_op(&mut self, _: &UnOp, o1: &Operand) { + fn visit_unary_op(&mut self, unop: &UnOp, o1: &Operand) { + match unop { + UnOp::Not | UnOp::Neg | UnOp::Cast(CastKind::Integer(_, _)) => (), + UnOp::Cast(CastKind::FnPtr(src, tgt)) => { + self.visit_ty(src); + self.visit_ty(tgt); + } + UnOp::ArrayToSlice(_, ty, cg) => { + self.visit_ty(ty); + self.visit_const_generic(cg); + } + } self.visit_operand(o1) } @@ -431,44 +477,58 @@ pub trait ExprVisitor: crate::types::TypeVisitor { // We could generalize and introduce auxiliary functions for // the various cases - this is not necessary for now match ak { - Tuple => (), - Option(_, ty) => self.visit_ty(ty), - Range(ty) => self.visit_ty(ty), - Adt(adt_id, _, _, tys, cgs) => { - self.visit_type_decl_id(adt_id); - for ty in tys { - self.visit_ty(ty); - } - for cg in cgs { - self.visit_const_generic(cg); - } + Adt(adt_id, _, generics) => { + self.visit_type_id(adt_id); + self.visit_generic_args(generics); } Array(ty, cg) => { self.visit_ty(ty); self.visit_const_generic(cg); } + Closure(fn_id) => self.visit_fun_decl_id(fn_id), } } fn visit_global(&mut self, _: &GlobalDeclId::Id) {} - fn visit_len(&mut self, p: &Place, _ty: &ETy, _cg: &Option) { - self.visit_place(p) + fn visit_len(&mut self, p: &Place, ty: &Ty, cg: &Option) { + self.visit_place(p); + self.visit_ty(ty); + match cg { + Some(cg) => self.visit_const_generic(cg), + None => (), + } + } + + fn visit_repeat(&mut self, op: &Operand, ty: &Ty, cg: &ConstGeneric) { + self.visit_operand(op); + self.visit_ty(ty); + self.visit_const_generic(cg); } fn visit_call(&mut self, c: &Call) { - self.visit_fun_id(&c.func); - // We ignore the regions which are erased - for t in &c.type_args { - self.visit_ty(t); - } - for cg in &c.const_generic_args { - self.visit_const_generic(cg); - } - for o in &c.args { + let Call { + func, + args, + dest, + } = c; + self.visit_fn_ptr(func); + for o in args { self.visit_operand(o); } - self.visit_place(&c.dest); + self.visit_place(dest); + } + + fn visit_fn_ptr(&mut self, fn_ptr: &FnPtr) { + let FnPtr { func, generics, trait_and_method_generic_args } = fn_ptr; + self.visit_fun_id_or_trait_ref(func); + self.visit_generic_args(generics); + match trait_and_method_generic_args { + None => (), + Some(generics) => { + self.visit_generic_args(generics); + } + } } fn visit_fun_id(&mut self, fun_id: &FunId) { @@ -478,8 +538,22 @@ pub trait ExprVisitor: crate::types::TypeVisitor { } } - fn visit_fun_decl_id(&mut self, fid: &FunDeclId::Id) {} - fn visit_assumed_fun_id(&mut self, fid: &AssumedFunId) {} + fn visit_fun_id_or_trait_ref(&mut self, fun_id: &FunIdOrTraitMethodRef) { + use FunIdOrTraitMethodRef::*; + match fun_id { + Fun(fun_id) => self.visit_fun_id(fun_id), + Trait(trait_ref, method_id, fun_decl_id) => { + self.visit_trait_ref(trait_ref); + self.visit_trait_method_name(method_id); + self.visit_fun_decl_id(fun_decl_id); + } + } + } + + fn visit_trait_method_name(&mut self, _: &TraitItemName) {} + + fn visit_fun_decl_id(&mut self, _: &FunDeclId::Id) {} + fn visit_assumed_fun_id(&mut self, _: &AssumedFunId) {} } } // make_generic_in_borrows diff --git a/charon/src/extract_global_assignments.rs b/charon/src/extract_global_assignments.rs deleted file mode 100644 index acd2c1ae..00000000 --- a/charon/src/extract_global_assignments.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! This module extracts globals from operands to put them in separate let bindings. -//! We do so to increase the sequentiality of LLBC and reduce the cases to handle -//! in the operands, making the formalisation less complex and easing the functional -//! translation in Aeneas. -//! -//! It also extracts statics fom operands for the same reason, because we want -//! to treat them as globals in (U)LLBC. -//! To do this, we add a new variable to reference the static: they are accessed -//! by reference in MIR, whereas globals are accessed by value. - -use crate::expressions::*; -use crate::meta::Meta; -use crate::types::*; -use crate::ullbc_ast::{ - iter_function_bodies, iter_global_bodies, make_locals_generator, CtxNames, FunDecls, - GlobalDecls, RawStatement, Statement, -}; -use crate::ullbc_ast_utils::body_transform_operands; -use crate::values::VarId; - -fn deref_static_type(ref_ty: &ETy) -> &ETy { - match ref_ty { - Ty::Ref(ErasedRegion::Erased, ty, RefKind::Shared) => ty, - _ => unreachable!( - "expected shared reference for static id type, got {:?}", - ref_ty - ), - } -} - -/// If the operand is a global identifier, push an `AssignGlobal` statement -/// that binds a new variable to the global and move it in the operand: -/// `... const X ...` -/// becomes -/// `let x0 = X;` -/// `... move x0 ...` -/// -/// If the operand is a static global identifier, also add an indirection to take -/// a reference on it: -/// `... const X ...` -/// becomes -/// `let x0 = X;` -/// `let x1 = &X;` -/// `... move x1 ...` -fn extract_operand_global_var VarId::Id>( - meta: &Meta, - nst: &mut Vec, - op: &mut Operand, - make_new_var: &mut F, -) { - // Check for early return - let (ty, c) = match op { - Operand::Const(ty, c) => (ty, c), - _ => return, - }; - - let var = match *c { - OperandConstantValue::Literal(_) | OperandConstantValue::Var(..) => return, - OperandConstantValue::Adt(_, _) => { - unreachable!("Constant ADTs should have been replaced by now") - } - OperandConstantValue::ConstantId(global_id) => { - let var = make_new_var(ty.clone()); - nst.push(Statement::new( - *meta, - RawStatement::Assign(Place::new(var), Rvalue::Global(global_id)), - )); - var - } - OperandConstantValue::StaticId(global_id) => { - let var = make_new_var(deref_static_type(ty).clone()); - let var_ref = make_new_var(ty.clone()); - let rvalue = Rvalue::Ref(Place::new(var), BorrowKind::Shared); - nst.push(Statement::new( - *meta, - RawStatement::Assign(Place::new(var), Rvalue::Global(global_id)), - )); - nst.push(Statement::new( - *meta, - RawStatement::Assign(Place::new(var_ref), rvalue), - )); - var_ref - } - }; - *op = Operand::Move(Place::new(var)); -} - -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { - for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { - trace!( - "# About to extract global assignments: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) - ); - - let mut f = make_locals_generator(&mut b.locals); - body_transform_operands(&mut b.body, &mut |meta, nst, op| { - extract_operand_global_var(meta, nst, op, &mut f) - }); - } -} diff --git a/charon/src/gast.rs b/charon/src/gast.rs index 39c1f4f7..fcf2efe7 100644 --- a/charon/src/gast.rs +++ b/charon/src/gast.rs @@ -1,22 +1,17 @@ //! Definitions common to [crate::ullbc_ast] and [crate::llbc_ast] -#![allow(dead_code)] - -pub use crate::expressions::{Operand, Place}; +pub use crate::expressions::*; pub use crate::gast_utils::*; use crate::meta::Meta; -use crate::names::FunName; -use crate::names::GlobalName; -use crate::regions_hierarchy::RegionGroups; +use crate::names::Name; pub use crate::types::GlobalDeclId; +pub use crate::types::TraitClauseId; use crate::types::*; -use crate::values::*; +pub use crate::types::{ + GenericArgs, GenericParams, TraitDeclId, TraitImplId, TraitInstanceId, TraitRef, +}; use macros::generate_index_type; -use macros::{EnumAsGetters, EnumIsA, VariantName}; use serde::Serialize; -// TODO: move this definition -pub static TAB_INCR: &str = " "; - generate_index_type!(FunDeclId); /// A variable @@ -28,47 +23,14 @@ pub struct Var { /// through desugaring. pub name: Option, /// The variable type - pub ty: ETy, -} - -/// A function signature. -/// Note that a signature uses unerased lifetimes, while function bodies (and -/// execution) use erased lifetimes. -/// We need the functions' signatures *with* the region parameters in order -/// to correctly abstract those functions (number and signature of the backward -/// functions) - we only use regions for this purpose. -#[derive(Debug, Clone, Serialize)] -pub struct FunSig { - pub region_params: RegionVarId::Vector, - /// The region parameters contain early bound and late bound parameters. - /// The early bound regions are those introduced by the `impl` block (if - /// this definition is defined in an `impl` block), the late bound regions - /// are those introduced by the function itself. - /// For example, in: - /// ```text - /// impl<'a> Foo<'a> { - /// fn bar<'b>(...) -> ... { ... } - /// } - /// ``` - /// `'a` is early-bound while `'b` is late-bound. - pub num_early_bound_regions: usize, - pub type_params: TypeVarId::Vector, - pub const_generic_params: ConstGenericVarId::Vector, - pub inputs: Vec, - pub output: RTy, - /// The lifetime's hierarchy between the different regions. - /// We initialize it to a dummy value, and compute it once the whole - /// crate has been translated from MIR. - /// - /// TODO: move to Aeneas. - pub regions_hierarchy: RegionGroups, + pub ty: Ty, } /// An expression body. /// TODO: arg_count should be stored in GFunDecl below. But then, /// the print is obfuscated and Aeneas may need some refactoring. #[derive(Debug, Clone, Serialize)] -pub struct GExprBody { +pub struct GExprBody { pub meta: Meta, /// The number of local variables used for the input arguments. pub arg_count: usize, @@ -81,16 +43,64 @@ pub struct GExprBody { pub body: T, } +/// Function kind: "regular" function, trait method declaration, etc. +/// +/// Example: +/// ======== +/// ```text +/// trait Foo { +/// fn bar(x : u32) -> u32; // trait method: declaration +/// +/// fn baz(x : bool) -> bool { x } // trait method: provided +/// } +/// +/// impl Foo for ... { +/// fn bar(x : u32) -> u32 { x } // trait method: implementation +/// } +/// +/// fn test(...) { ... } // regular +/// +/// impl Type { +/// fn test(...) { ... } // regular +/// } +/// ``` +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub enum FunKind { + /// A "normal" function + Regular, + /// Trait method implementation + TraitMethodImpl { + /// The trait implementation block the method belongs to + impl_id: TraitImplId::Id, + /// The id of the trait decl the method belongs to + trait_id: TraitDeclId::Id, + /// The name of the method + method_name: TraitItemName, + /// True if this function re-implements a provided method + provided: bool, + }, + /// Trait method declaration + TraitMethodDecl(TraitDeclId::Id, TraitItemName), + /// Trait method provided function (trait method declaration which defines + /// a default implementation at the same time) + TraitMethodProvided(TraitDeclId::Id, TraitItemName), +} + /// A function definition #[derive(Debug, Clone, Serialize)] -pub struct GFunDecl { +pub struct GFunDecl { pub def_id: FunDeclId::Id, /// The meta data associated with the declaration. pub meta: Meta, - pub name: FunName, + /// [true] if the decl is a local decl, [false] if it comes from + /// an external crate. + pub is_local: bool, + pub name: Name, /// The signature contains the inputs/output types *with* non-erased regions. /// It also contains the list of region and type parameters. pub signature: FunSig, + /// The function kind: "regular" function, trait method declaration, etc. + pub kind: FunKind, /// The function body, in case the function is not opaque. /// Opaque functions are: external functions, or local functions tagged /// as opaque. @@ -99,135 +109,142 @@ pub struct GFunDecl { /// A global variable definition, either opaque or transparent. #[derive(Debug, Clone, Serialize)] -pub struct GGlobalDecl { +pub struct GGlobalDecl { pub def_id: GlobalDeclId::Id, /// The meta data associated with the declaration. pub meta: Meta, - pub name: GlobalName, - pub ty: ETy, + /// [true] if the decl is a local decl, [false] if it comes from + /// an external crate. + pub is_local: bool, + pub name: Name, + pub ty: Ty, pub body: Option>, } -/// A function identifier. See [crate::ullbc_ast::Terminator] -#[derive(Debug, Clone, EnumIsA, EnumAsGetters, VariantName, Serialize)] -pub enum FunId { - /// A "regular" function (function local to the crate, external function - /// not treated as a primitive one). - Regular(FunDeclId::Id), - /// A primitive function, coming from a standard library (for instance: - /// `alloc::boxed::Box::new`). - /// TODO: rename to "Primitive" - Assumed(AssumedFunId), -} +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TraitItemName(pub String); -/// An assumed function identifier, identifying a function coming from a -/// standard library. -#[derive(Debug, Clone, Copy, EnumIsA, EnumAsGetters, VariantName, Serialize)] -pub enum AssumedFunId { - /// `core::mem::replace` - Replace, - /// `alloc::boxed::Box::new` - BoxNew, - /// `core::ops::deref::Deref::>::deref` - BoxDeref, - /// `core::ops::deref::DerefMut::>::deref_mut` - BoxDerefMut, - /// `alloc::alloc::box_free` - /// This is actually an unsafe function, but the rust compiler sometimes - /// introduces it when going to MIR. +/// A trait **declaration**. +/// +/// For instance: +/// ```text +/// trait Foo { +/// type Bar; +/// +/// fn baz(...); // required method (see below) +/// +/// fn test() -> bool { true } // provided method (see below) +/// } +/// ``` +/// +/// In case of a trait declaration, we don't include the provided methods (the methods +/// with a default implementation): they will be translated on a per-need basis. This is +/// important for two reasons: +/// - this makes the trait definitions a lot smaller (the Iterator trait +/// has *one* declared function and more than 70 provided functions) +/// - this is important for the external traits, whose provided methods +/// often use features we don't support yet +/// +/// Remark: +/// In Aeneas, we still translate the provided methods on an individual basis, +/// and in such a way thay they take as input a trait instance. This means that +/// we can use default methods *but*: +/// - implementations of required methods shoudln't call default methods +/// - trait implementations shouldn't redefine required methods +/// The use case we have in mind is [std::iter::Iterator]: it declares one required +/// method (`next`) that should be implemented for every iterator, and defines many +/// helpers like `all`, `map`, etc. that shouldn't be re-implemented. +/// Of course, this forbids other useful use cases such as visitors implemented +/// by means of traits. +#[allow(clippy::type_complexity)] +#[derive(Debug, Clone, Serialize)] +pub struct TraitDecl { + pub def_id: TraitDeclId::Id, + /// [true] if the decl is a local decl, [false] if it comes from + /// an external crate. + pub is_local: bool, + pub name: Name, + pub meta: Meta, + pub generics: GenericParams, + pub preds: Predicates, + /// The "parent" clauses: the supertraits. /// - /// Also, in practice, deallocation is performed as follows in MIR: - /// ```text - /// alloc::alloc::box_free::( - /// move (b.0: std::ptr::Unique), - /// move (b.1: std::alloc::Global)) - /// ``` - /// When translating from MIR to ULLBC, we do as if the MIR was actually the - /// following (this is hardcoded - see [crate::register] and [crate::translate_functions_to_ullbc]): + /// Supertraits are actually regular where clauses, but we decided to have + /// a custom treatment. /// ```text - /// alloc::alloc::box_free::(move b) + /// trait Foo : Bar { + /// ^^^ + /// supertrait, that we treat as a parent predicate + /// } /// ``` + /// TODO: actually, as of today, we consider that all trait clauses of + /// trait declarations are parent clauses. + pub parent_clauses: TraitClauseId::Vector, + /// The associated constants declared in the trait. /// - /// Also see the comments in [crate::assumed::type_to_used_params]. - BoxFree, - /// `alloc::vec::Vec::new` - VecNew, - /// `alloc::vec::Vec::push` - VecPush, - /// `alloc::vec::Vec::insert` - VecInsert, - /// `alloc::vec::Vec::len` - VecLen, - /// `core::ops::index::Index::index, usize>` - VecIndex, - /// `core::ops::index::IndexMut::index_mut, usize>` - VecIndexMut, - /// Converted from [ProjectionElem::Index]. + /// The optional id is for the default value. + pub consts: Vec<(TraitItemName, (Ty, Option))>, + /// The associated types declared in the trait. + pub types: Vec<(TraitItemName, (Vec, Option))>, + /// The *required* methods. /// - /// Signature: `fn(&[T;N], usize) -> &T` - ArrayIndexShared, - /// Converted from [ProjectionElem::Index]. + /// The required methods are the methods declared by the trait but with + /// no default implementation. + pub required_methods: Vec<(TraitItemName, FunDeclId::Id)>, + /// The *provided* methods. /// - /// Signature: `fn(&mut [T;N], usize) -> &mut T` - ArrayIndexMut, - /// Cast an array as a slice. + /// The provided methods are the methods with a default implementation. /// - /// Converted from [UnOp::ArrayToSlice] - ArrayToSliceShared, - /// Cast an array as a slice. + /// We include the [FunDeclId::Id] identifiers *only* for the local + /// trait declarations. Otherwise, it would mean we extract *all* the + /// provided methods, which is not something we want to do *yet* for + /// the external traits. /// - /// Converted from [UnOp::ArrayToSlice] - ArrayToSliceMut, - /// Take a subslice from an array. - /// - /// Introduced by disambiguating the `Index::index` trait (takes a range - /// as argument). - /// - /// TODO: there are a lot of shared/mut version. Parameterize them with - /// a mutability attribute? - ArraySubsliceShared, - /// Take a subslice from an array. - /// - /// Introduced by disambiguating the `Index::index` trait (takes a range - /// as argument). - ArraySubsliceMut, - /// Remark: when we write `a.len()` in Rust, where `a` is an array, the - /// statement is desugared to a conversion from array to slice, followed - /// by a call to the `len` function for slices. - /// - /// Signature: `fn(&[T]) -> usize` - SliceLen, - /// Converted from [ProjectionElem::Index]. - /// - /// Signature: `fn(&[T], usize) -> &T` - SliceIndexShared, - /// Converted from [ProjectionElem::Index]. - /// - /// Signature: `fn(&mut [T], usize) -> &mut T` - SliceIndexMut, - /// Take a subslice from a slice. - /// - /// Introduced by disambiguating the `Index::index` trait (takes a range - /// as argument). - SliceSubsliceShared, - /// Take a subslice from a slice. - /// - /// Introduced by disambiguating the `Index::index` trait (takes a range - /// as argument). - SliceSubsliceMut, + /// TODO: allow to optionnaly extract information. For instance: attempt + /// to extract, and fail nicely if we don't succeed (definition not in + /// the supported subset, etc.). + pub provided_methods: Vec<(TraitItemName, Option)>, +} + +/// A trait **implementation**. +/// +/// For instance: +/// ```text +/// impl Foo for List { +/// type Bar = ... +/// +/// fn baz(...) { ... } +/// } +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct TraitImpl { + pub def_id: TraitImplId::Id, + /// [true] if the decl is a local decl, [false] if it comes from + /// an external crate. + pub is_local: bool, + pub name: Name, + pub meta: Meta, + /// The information about the implemented trait. + /// Note that this contains the instantiation of the "parent" + /// clauses. + pub impl_trait: TraitDeclRef, + pub generics: GenericParams, + pub preds: Predicates, + /// The trait references for the parent clauses (see [TraitDecl]). + pub parent_trait_refs: TraitClauseId::Vector, + /// The associated constants declared in the trait. + pub consts: Vec<(TraitItemName, (Ty, GlobalDeclId::Id))>, + /// The associated types declared in the trait. + pub types: Vec<(TraitItemName, (Vec, Ty))>, + /// The implemented required methods + pub required_methods: Vec<(TraitItemName, FunDeclId::Id)>, + /// The re-implemented provided methods + pub provided_methods: Vec<(TraitItemName, FunDeclId::Id)>, } -/// TODO: factor out with [Rvalue] #[derive(Debug, Clone, Serialize)] pub struct Call { - pub func: FunId, - /// Technically this is useless, but we still keep it because we might - /// want to introduce some information (and the way we encode from MIR - /// is as simple as possible - and in MIR we also have a vector of erased - /// regions). - pub region_args: Vec, - pub type_args: Vec, - pub const_generic_args: Vec, + pub func: FnPtr, pub args: Vec, pub dest: Place, } diff --git a/charon/src/gast_utils.rs b/charon/src/gast_utils.rs index e2811284..3c13bed7 100644 --- a/charon/src/gast_utils.rs +++ b/charon/src/gast_utils.rs @@ -1,19 +1,17 @@ //! Implementations for [crate::gast] -#![allow(dead_code)] -use crate::expressions::*; +use crate::common::TAB_INCR; +pub use crate::expressions_utils::ExprFormatter; use crate::formatter::Formatter; use crate::gast::*; use crate::names::Name; use crate::types::*; use crate::values::*; -use serde::Serialize; use std::cmp::max; -use std::fmt::Debug; /// Iterate on the declarations' non-empty bodies with their corresponding name and type. /// TODO: generalize this with visitors -pub fn iter_function_bodies( +pub fn iter_function_bodies( funs: &mut FunDeclId::Map>, ) -> impl Iterator)> { funs.iter_mut().flat_map(|f| match f.body.as_mut() { @@ -25,7 +23,7 @@ pub fn iter_function_bodies( /// Iterate on the declarations' non-empty bodies with their corresponding name and type. /// Same as [iter_function_bodies] (but the `flat_map` lambda cannot be generic). /// TODO: generalize this with visitors -pub fn iter_global_bodies( +pub fn iter_global_bodies( globals: &mut GlobalDeclId::Map>, ) -> impl Iterator)> { globals.iter_mut().flat_map(|g| match g.body.as_mut() { @@ -36,7 +34,7 @@ pub fn iter_global_bodies( /// Makes a lambda that generates a new variable id, pushes a new variable in /// the body locals with the given type and returns its id. -pub fn make_locals_generator(locals: &mut VarId::Vector) -> impl FnMut(ETy) -> VarId::Id + '_ { +pub fn make_locals_generator(locals: &mut VarId::Vector) -> impl FnMut(Ty) -> VarId::Id + '_ { let mut next_id = locals.iter().fold(VarId::ZERO, |id, v| max(id, v.index)); move |ty| { next_id.incr(); @@ -50,6 +48,12 @@ pub fn make_locals_generator(locals: &mut VarId::Vector) -> impl FnMut(ETy) } } +impl FunDeclId::Id { + pub fn to_pretty_string(self) -> String { + format!("@Fun{self}") + } +} + impl std::string::ToString for Var { fn to_string(&self) -> String { let id = self.index.to_pretty_string(); @@ -63,7 +67,7 @@ impl std::string::ToString for Var { } impl VarId::Vector { - pub fn fresh_var(&mut self, name: Option, ty: ETy) -> VarId::Id { + pub fn fresh_var(&mut self, name: Option, ty: Ty) -> VarId::Id { let index = VarId::Id::new(self.len()); self.push_back(Var { index, name, ty }); index @@ -73,7 +77,7 @@ impl VarId::Vector { impl Var { /// Substitute the region parameters and type variables and return /// the resulting variable - pub fn substitute(&self, subst: &ETypeSubst, cgsubst: &ConstGenericSubst) -> Var { + pub fn substitute(&self, subst: &TypeSubst, cgsubst: &ConstGenericSubst) -> Var { Var { index: self.index, name: self.name.clone(), @@ -82,53 +86,161 @@ impl Var { } } -pub fn fmt_call<'a, 'b, T>( - ctx: &'b T, - func: &'a FunId, - region_args: &'a Vec, - type_args: &'a Vec, - const_generic_args: &'a Vec, - args: &'a [Operand], -) -> String +impl TraitDecl { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let name = self.name.fmt_with_ctx(ctx); + let (generics, trait_clauses) = self.generics.fmt_with_ctx_with_trait_clauses(ctx); + let clauses = fmt_where_clauses_with_ctx(ctx, "", &None, trait_clauses, &self.preds); + + let items = { + let items = self + .parent_clauses + .iter() + .map(|c| { + format!( + "{TAB_INCR}parent_clause_{} : {}\n", + c.clause_id.to_string(), + c.fmt_with_ctx(ctx) + ) + }) + .chain( + self.consts + .iter() + .map(|(name, (ty, opt_id))| { + let ty = ty.fmt_with_ctx(ctx); + match opt_id { + None => format!("{TAB_INCR}const {name} : {ty}\n"), + Some(id) => { + format!( + "{TAB_INCR}const {name} : {ty} = {}\n", + ctx.format_object(*id) + ) + } + } + }) + .chain(self.types.iter().map(|(name, (trait_clauses, opt_ty))| { + let trait_clauses: Vec<_> = + trait_clauses.iter().map(|x| x.fmt_with_ctx(ctx)).collect(); + let clauses = fmt_where_clauses( + &format!("{TAB_INCR}{TAB_INCR}"), + 0, + trait_clauses, + ); + match opt_ty { + None => format!("{TAB_INCR}type {name}{clauses}\n"), + Some(ty) => { + format!( + "{TAB_INCR}type {name} = {}{clauses}\n", + ty.fmt_with_ctx(ctx) + ) + } + } + })) + .chain(self.required_methods.iter().map(|(name, f)| { + format!("{TAB_INCR}fn {name} : {}\n", ctx.format_object(*f)) + })) + .chain(self.provided_methods.iter().map(|(name, f)| match f { + None => format!("{TAB_INCR}fn {name}\n"), + Some(f) => format!("{TAB_INCR}fn {name} : {}\n", ctx.format_object(*f)), + })), + ) + .collect::>(); + if items.is_empty() { + "".to_string() + } else { + format!("\n{{\n{}}}", items.join("")) + } + }; + + format!("trait {name}{generics}{clauses}{items}") + } +} + +impl TraitImpl { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let name = self.name.fmt_with_ctx(ctx); + let (generics, trait_clauses) = self.generics.fmt_with_ctx_with_trait_clauses(ctx); + let clauses = fmt_where_clauses_with_ctx(ctx, "", &None, trait_clauses, &self.preds); + + let items = { + let items = self + .parent_trait_refs + .iter() + .enumerate() + .map(|(i, clause)| { + let i = TraitClauseId::Id::new(i); + format!( + "{TAB_INCR}parent_clause{i} = {}\n", + clause.fmt_with_ctx(ctx) + ) + }) + .chain(self.consts.iter().map(|(name, (ty, id))| { + format!( + "{TAB_INCR}const {name} : {} = {}\n", + ty.fmt_with_ctx(ctx), + ctx.format_object(*id) + ) + })) + .chain(self.types.iter().map(|(name, (trait_refs, ty))| { + let trait_refs = trait_refs + .iter() + .map(|x| x.fmt_with_ctx(ctx)) + .collect::>() + .join(", "); + format!( + "{TAB_INCR}type {name} = {} with [{}]\n", + ty.fmt_with_ctx(ctx), + trait_refs + ) + })) + .chain( + self.required_methods + .iter() + .chain(self.provided_methods.iter()) + .map(|(name, f)| { + format!("{TAB_INCR}fn {name} = {}\n", ctx.format_object(*f)) + }), + ) + .collect::>(); + if items.is_empty() { + "".to_string() + } else { + format!("\n{{\n{}}}", items.join("")) + } + }; + + let impl_trait = self.impl_trait.fmt_with_ctx(ctx); + format!("impl{generics} {name}{generics} : {impl_trait}{clauses}{items}") + } +} + +/// Format a function call. +/// We return the pair: (function call, comment) +pub fn fmt_call(ctx: &T, call: &Call) -> (String, Option) where - T: Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + T: ExprFormatter, { - let rt_args = if region_args.len() + type_args.len() + const_generic_args.len() == 0 { - "".to_string() - } else { - let regions_s: Vec = region_args.iter().map(|x| x.to_string()).collect(); - let mut types_s: Vec = type_args.iter().map(|x| x.fmt_with_ctx(ctx)).collect(); - let mut cgs_s: Vec = const_generic_args - .iter() - .map(|x| x.fmt_with_ctx(ctx)) - .collect(); - let mut s = regions_s; - s.append(&mut types_s); - s.append(&mut cgs_s); - format!("<{}>", s.join(", ")) - }; - let args: Vec = args.iter().map(|x| x.fmt_with_ctx(ctx)).collect(); - let args = args.join(", "); + let trait_and_method_generic_args = call + .func + .trait_and_method_generic_args + .as_ref() + .map(|generics| generics.fmt_with_ctx_split_trait_refs(ctx)); - let f = match func { - FunId::Regular(def_id) => format!("{}{}", ctx.format_object(*def_id), rt_args), - FunId::Assumed(assumed) => { - format!("@{}{rt_args}", assumed.variant_name()) - } - }; + let f = call.func.fmt_with_ctx(ctx); - format!("{f}({args})") + let args: Vec = call.args.iter().map(|x| x.fmt_with_ctx(ctx)).collect(); + let args = args.join(", "); + + (format!("{f}({args})"), trait_and_method_generic_args) } -impl GExprBody { +impl GExprBody { /// This is an auxiliary function for printing definitions. One may wonder /// why we require a formatter to format, for instance, (type) var ids, /// because the function definition already has the information to print @@ -136,18 +248,9 @@ impl GExprBody { /// generic auxiliary function, then apply it on an evaluation context /// properly initialized (with the information contained in the function /// definition). See [`fmt_with_decls`](crate::ullbc_ast::FunDecl::fmt_with_decls). - pub fn fmt_with_ctx<'a, 'b, 'c, C>(&'a self, tab: &'b str, ctx: &'c C) -> String + pub fn fmt_with_ctx<'a, C>(&'a self, tab: &str, ctx: &C) -> String where - C: Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - + Formatter<&'a T>, + C: ExprFormatter + Formatter<&'a T>, { // Format the local variables let mut locals: Vec = Vec::new(); @@ -194,133 +297,9 @@ impl GExprBody { } } -impl FunSig { - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &'a T) -> String - where - T: Formatter - + Formatter - + Formatter - + Formatter<&'a Region> - + Formatter - + Formatter, - { - // Type parameters - let params = { - let regions: Vec = self.region_params.iter().map(|x| x.to_string()).collect(); - let mut types: Vec = self.type_params.iter().map(|x| x.to_string()).collect(); - let mut cgs: Vec = self - .const_generic_params - .iter() - .map(|x| x.to_string()) - .collect(); - let mut params = regions; - params.append(&mut types); - params.append(&mut cgs); - if params.is_empty() { - "".to_string() - } else { - format!("<{}>", params.join(", ")) - } - }; - - // Arguments - let mut args: Vec = Vec::new(); - for ty in &self.inputs { - args.push(ty.fmt_with_ctx(ctx).to_string()); - } - let args = args.join(", "); - - // Return type - let ret_ty = &self.output; - let ret_ty = if ret_ty.is_unit() { - "".to_string() - } else { - format!(" -> {}", ret_ty.fmt_with_ctx(ctx)) - }; - - // Regions hierarchy - let regions_hierarchy: Vec = self - .regions_hierarchy - .iter() - .map(|rg| rg.fmt_with_ctx(ctx)) - .collect(); - let regions_hierarchy = regions_hierarchy.join("\n"); - - // Put everything together - format!("fn{params}({args}){ret_ty}\n\nRegions hierarchy:\n{regions_hierarchy}",) - } -} - -pub struct FunSigFormatter<'a, GD> { - pub ty_ctx: &'a TypeDecls, - pub global_ctx: &'a GD, - pub sig: &'a FunSig, -} - -impl<'a, GD> Formatter for FunSigFormatter<'a, GD> { - fn format_object(&self, id: TypeVarId::Id) -> String { - self.sig.type_params.get(id).unwrap().to_string() - } -} +pub trait GFunDeclFormatter<'a, Body: 'a> = ExprFormatter + Formatter<&'a Body>; -impl<'a, GD> Formatter for FunSigFormatter<'a, GD> { - fn format_object(&self, id: RegionVarId::Id) -> String { - self.sig.region_params.get(id).unwrap().to_string() - } -} - -impl<'a, GD> Formatter<&Region> for FunSigFormatter<'a, GD> { - fn format_object(&self, r: &Region) -> String { - r.fmt_with_ctx(self) - } -} - -impl<'a, GD> Formatter for FunSigFormatter<'a, GD> { - fn format_object(&self, id: TypeDeclId::Id) -> String { - self.ty_ctx.format_object(id) - } -} - -impl<'a, GD> Formatter for FunSigFormatter<'a, GD> -where - GD: Formatter, -{ - fn format_object(&self, id: GlobalDeclId::Id) -> String { - self.global_ctx.format_object(id) - } -} - -impl<'a, GD> Formatter for FunSigFormatter<'a, GD> { - fn format_object(&self, id: ConstGenericVarId::Id) -> String { - match self.sig.const_generic_params.get(id) { - Option::None => { - error!("Could not find a ConsGenericVarId::Id for pretty-printing"); - id.to_pretty_string() - } - Option::Some(cg_var) => cg_var.to_string(), - } - } -} - -impl FunSig { - pub fn fmt_with_decls>( - &self, - ty_ctx: &TypeDecls, - global_ctx: &GD, - ) -> String { - // Initialize the formatting context - let ctx = FunSigFormatter { - ty_ctx, - global_ctx, - sig: self, - }; - - // Use the context for printing - self.fmt_with_ctx(&ctx) - } -} - -impl GFunDecl { +impl GFunDecl { /// This is an auxiliary function for printing definitions. One may wonder /// why we require a formatter to format, for instance, (type) var ids, /// because the function definition already has the information to print @@ -328,61 +307,22 @@ impl GFunDecl { /// generic auxiliary function, then apply it on an evaluation context /// properly initialized (with the information contained in the function /// definition). See [`fmt_with_decls`](crate::ullbc_ast::FunDecl::fmt_with_decls). - pub fn gfmt_with_ctx<'a, 'b, 'c, T1, T2>( - &'a self, - tab: &'b str, - sig_ctx: &'c T1, - body_ctx: &'c T2, - ) -> String + pub fn gfmt_with_ctx<'a, C>(&'a self, tab: &str, ctx: &C) -> String where - T1: Formatter - + Formatter - + Formatter<&'a Region> - + Formatter - + Formatter, - T2: Formatter - + Formatter - + Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - + Formatter<&'a T>, + C: GFunDeclFormatter<'a, T>, { + // Unsafe keyword + let unsafe_kw = if self.signature.is_unsafe { + "unsafe ".to_string() + } else { + "".to_string() + }; + // Function name - let name = self.name.to_string(); + let name = self.name.fmt_with_ctx(ctx); - // Type parameters - let params = { - let regions: Vec = self - .signature - .region_params - .iter() - .map(|x| x.to_string()) - .collect(); - let mut types: Vec = self - .signature - .type_params - .iter() - .map(|x| x.to_string()) - .collect(); - let mut cgs: Vec = self - .signature - .const_generic_params - .iter() - .map(|x| x.to_string()) - .collect(); - let mut params = regions; - params.append(&mut types); - params.append(&mut cgs); - if params.is_empty() { - "".to_string() - } else { - format!("<{}>", params.join(", ")) - } - }; + // Generic parameters + let (params, trait_clauses) = self.signature.generics.fmt_with_ctx_with_trait_clauses(ctx); // Arguments let mut args: Vec = Vec::new(); @@ -391,12 +331,7 @@ impl GFunDecl { let id = VarId::Id::new(i + 1); let arg_ty = &self.signature.inputs.get(i).unwrap(); args.push( - format!( - "{}: {}", - id.to_pretty_string(), - arg_ty.fmt_with_ctx(sig_ctx) - ) - .to_string(), + format!("{}: {}", id.to_pretty_string(), arg_ty.fmt_with_ctx(ctx)).to_string(), ); } let args = args.join(", "); @@ -406,28 +341,41 @@ impl GFunDecl { let ret_ty = if ret_ty.is_unit() { "".to_string() } else { - format!(" -> {}", ret_ty.fmt_with_ctx(sig_ctx)) + format!(" -> {}", ret_ty.fmt_with_ctx(ctx)) }; + // Predicates + let preds = fmt_where_clauses_with_ctx( + ctx, + tab, + &self.signature.parent_params_info, + trait_clauses, + &self.signature.preds, + ); + // Case disjunction on the presence of a body (transparent/opaque definition) match &self.body { Option::None => { // Put everything together - format!("{tab}fn {name}{params}({args}){ret_ty}") + format!("{tab}{unsafe_kw}fn {name}{params}({args}){ret_ty}{preds}") } Option::Some(body) => { // Body let body_tab = format!("{tab}{TAB_INCR}"); - let body = body.fmt_with_ctx(&body_tab, body_ctx); + let body = body.fmt_with_ctx(&body_tab, ctx); // Put everything together - format!("{tab}fn {name}{params}({args}){ret_ty} {{\n{body}\n{tab}}}",) + format!( + "{tab}{unsafe_kw}fn {name}{params}({args}){ret_ty}{preds}\n{tab}{{\n{body}\n{tab}}}", + ) } } } } -impl GGlobalDecl { +pub trait GGlobalDeclFormatter<'a, Body: 'a> = ExprFormatter + Formatter<&'a Body>; + +impl GGlobalDecl { /// This is an auxiliary function for printing definitions. One may wonder /// why we require a formatter to format, for instance, (type) var ids, /// because the global definition already has the information to print @@ -435,21 +383,12 @@ impl GGlobalDecl { /// generic auxiliary function, then apply it on an evaluation context /// properly initialized (with the information contained in the global /// definition). See [`fmt_with_decls`](crate::ullbc_ast::FunDecl::fmt_with_decls). - pub fn gfmt_with_ctx<'a, 'b, 'c, T>(&'a self, tab: &'b str, body_ctx: &'c T) -> String + pub fn gfmt_with_ctx<'a, C>(&'a self, tab: &str, ctx: &C) -> String where - T: Formatter - + Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - + Formatter<&'a CD>, + C: GGlobalDeclFormatter<'a, T>, { // Decl name - let name = self.name.to_string(); + let name = self.name.fmt_with_ctx(ctx); // Case disjunction on the presence of a body (transparent/opaque definition) match &self.body { @@ -460,7 +399,7 @@ impl GGlobalDecl { Option::Some(body) => { // Body let body_tab = format!("{tab}{TAB_INCR}"); - let body = body.fmt_with_ctx(&body_tab, body_ctx); + let body = body.fmt_with_ctx(&body_tab, ctx); // Put everything together format!("{tab}global {name} {{\n{body}\n{tab}}}") @@ -469,272 +408,14 @@ impl GGlobalDecl { } } -impl GGlobalDecl { - fn is_opaque(&self) -> bool { - self.body.is_none() - } -} - -pub struct GAstFormatter<'ctx, FD, GD> { - pub type_context: &'ctx TypeDecls, - pub fun_context: &'ctx FD, - pub global_context: &'ctx GD, - /// We may not have the list of type variables at hand (if we directly - /// pretty print a body, for instance). If this is the case, the - /// formatter will print the variable indices. - pub type_vars: Option<&'ctx TypeVarId::Vector>, - /// Same as for `type_vars`. - pub vars: Option<&'ctx VarId::Vector>, - /// Same as for `type_vars` - pub const_generic_vars: Option<&'ctx ConstGenericVarId::Vector>, -} - -pub struct CtxNames<'ctx> { - pub type_context: &'ctx TypeDecls, - pub fun_context: &'ctx FunDeclId::Map, - pub global_context: &'ctx GlobalDeclId::Map, -} - -impl<'ctx> CtxNames<'ctx> { - pub fn new( - type_context: &'ctx TypeDecls, - fun_context: &'ctx FunDeclId::Map, - global_context: &'ctx GlobalDeclId::Map, - ) -> Self { - CtxNames { - type_context, - fun_context, - global_context, - } - } -} - -impl<'ctx, FD, GD> GAstFormatter<'ctx, FD, GD> { - pub fn new( - type_context: &'ctx TypeDecls, - fun_context: &'ctx FD, - global_context: &'ctx GD, - type_vars: Option<&'ctx TypeVarId::Vector>, - vars: Option<&'ctx VarId::Vector>, - const_generic_vars: Option<&'ctx ConstGenericVarId::Vector>, - ) -> Self { - GAstFormatter { - type_context, - fun_context, - global_context, - type_vars, - vars, - const_generic_vars, - } - } -} - -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, id: VarId::Id) -> String { - if self.vars.is_some() { - let v = self.vars.unwrap().get(id).unwrap(); - v.to_string() - } else { - id.to_pretty_string() - } - } -} - -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, id: TypeVarId::Id) -> String { - if self.type_vars.is_some() { - self.type_vars.unwrap().get(id).unwrap().to_string() - } else { - id.to_pretty_string() - } - } -} - -/// For adt types -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, id: TypeDeclId::Id) -> String { - self.type_context.format_object(id) - } -} - -/// For enum values: `List::Cons` -impl<'ctx, FD, GD> Formatter<(TypeDeclId::Id, VariantId::Id)> for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, id: (TypeDeclId::Id, VariantId::Id)) -> String { - let (def_id, variant_id) = id; - let ctx = self.type_context; - // The definition may not be available yet, especially if we print-debug - // while translating the crate - match ctx.get(def_id) { - Option::None => format!( - "{}::{}", - def_id.to_pretty_string(), - variant_id.to_pretty_string() - ), - Option::Some(def) => { - let variants = def.kind.as_enum(); - let mut name = def.name.to_string(); - let variant_name = &variants.get(variant_id).unwrap().name; - name.push_str("::"); - name.push_str(variant_name); - name - } - } - } -} - -/// For struct/enum values: retrieve a field name -impl<'ctx, FD, GD> Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - for GAstFormatter<'ctx, FD, GD> -{ - fn format_object(&self, id: (TypeDeclId::Id, Option, FieldId::Id)) -> String { - let (def_id, opt_variant_id, field_id) = id; - let ctx = self.type_context; - // The definition may not be available yet, especially if we print-debug - // while translating the crate - match ctx.get(def_id) { - Option::None => match opt_variant_id { - Option::None => format!( - "{}::{}", - def_id.to_pretty_string(), - field_id.to_pretty_string() - ), - Option::Some(variant_id) => format!( - "{}::{}::{}", - def_id.to_pretty_string(), - variant_id.to_pretty_string(), - field_id.to_pretty_string() - ), - }, - Option::Some(gen_def) => match (&gen_def.kind, opt_variant_id) { - (TypeDeclKind::Enum(variants), Some(variant_id)) => { - let field = variants - .get(variant_id) - .unwrap() - .fields - .get(field_id) - .unwrap(); - match &field.name { - Option::Some(name) => name.clone(), - Option::None => field_id.to_string(), - } - } - (TypeDeclKind::Struct(fields), None) => { - let field = fields.get(field_id).unwrap(); - match &field.name { - Option::Some(name) => name.clone(), - Option::None => field_id.to_string(), - } - } - _ => unreachable!(), - }, - } - } -} - -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, id: ConstGenericVarId::Id) -> String { - if self.const_generic_vars.is_some() { - match self.const_generic_vars.unwrap().get(id) { - Option::None => { - error!("Could not find a ConsGenericVarId::Id for pretty-printing"); - id.to_pretty_string() - } - Option::Some(cg_var) => cg_var.to_string(), - } - } else { - id.to_pretty_string() - } - } -} - -impl<'ctx, FD, GD> Formatter<&ErasedRegion> for GAstFormatter<'ctx, FD, GD> { - fn format_object(&self, _: &ErasedRegion) -> String { - "'_".to_string() - } -} - -impl<'ctx, FD, GD> Formatter<&ETy> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, -{ - fn format_object(&self, ty: &ETy) -> String { - ty.fmt_with_ctx(self) - } -} - -impl<'ctx, FD, GD> Formatter<&Place> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, -{ - fn format_object(&self, p: &Place) -> String { - p.fmt_with_ctx(self) - } -} - -impl<'ctx, FD, GD> Formatter<&Operand> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, -{ - fn format_object(&self, op: &Operand) -> String { - op.fmt_with_ctx(self) - } -} - -impl<'ctx, FD, GD> Formatter<&Rvalue> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, -{ - fn format_object(&self, v: &Rvalue) -> String { - v.fmt_with_ctx(self) - } -} - -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> -where - FD: Formatter, -{ - fn format_object(&self, id: FunDeclId::Id) -> String { - self.fun_context.format_object(id) - } -} - -impl<'ctx, FD, GD> Formatter for GAstFormatter<'ctx, FD, GD> -where - GD: Formatter, -{ - fn format_object(&self, id: GlobalDeclId::Id) -> String { - self.global_context.format_object(id) - } -} - -pub(crate) struct FunNamesFormatter<'ctx> { - decls: &'ctx FunDeclId::Map, -} - -pub(crate) struct GlobalNamesFormatter<'ctx> { - decls: &'ctx GlobalDeclId::Map, -} - -impl<'ctx> FunNamesFormatter<'ctx> { - pub fn new(decls: &'ctx FunDeclId::Map) -> Self { - FunNamesFormatter { decls } - } -} - -impl<'ctx> Formatter for FunNamesFormatter<'ctx> { - fn format_object(&self, id: FunDeclId::Id) -> String { - self.decls.get(id).unwrap().clone() - } -} - -impl<'ctx> GlobalNamesFormatter<'ctx> { - pub fn new(decls: &'ctx GlobalDeclId::Map) -> Self { - GlobalNamesFormatter { decls } +impl FunIdOrTraitMethodRef { + pub(crate) fn mk_assumed(aid: AssumedFunId) -> Self { + Self::Fun(FunId::Assumed(aid)) } } -impl<'ctx> Formatter for GlobalNamesFormatter<'ctx> { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - self.decls.get(id).unwrap().clone() +impl std::fmt::Display for TraitItemName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", self.0) } } diff --git a/charon/src/generics.rs b/charon/src/generics.rs deleted file mode 100644 index 850e268d..00000000 --- a/charon/src/generics.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! This file contains various utilities to manipulate generics: -//! - instantiation of binders -//! - checks - -#![allow(dead_code)] -use crate::assumed; -use crate::names::trait_def_id_to_name; -use hashlink::linked_hash_map::LinkedHashMap; -use rustc_hir::def_id::DefId; -use rustc_middle::ty::{ - BoundRegion, Clause, FreeRegion, PredicateKind, Region, RegionKind, TyCtxt, -}; - -/// Instantiate the bound region variables in a binder, by turning the bound -/// regions variables into free region variables. Note that the indices used -/// for the free regions are the indices used for the bound regions: we do not -/// generate *fresh* regions in an ad-hoc manner. -/// -/// This is typically used when instantiating function signatures (but not only): -/// signatures are wrapped in a [`Binder`](rustc_middle::ty::Binder), which we -/// need to remove through a proper instantiation (we simply instantiate bound -/// variables with free variables). -/// -/// This code is inspired by [TyCtxt::liberate_late_bound_regions]. -/// -/// The rationale is as follows: -/// - it seems liberate_late_bound_regions is a proper way of retrieving -/// a signature where all the bound variables have been replaced with -/// free variables, so that we can study it easily (without having, for -/// instance, to deal with DeBruijn indices) -/// - my understanding of why it is enough to bind late-bound regions: the -/// early bound regions are not bound here (they are free), because -/// they reference regions introduced by the `impl` block (if this definition -/// is defined in an `impl` block - otherwise there are no early bound variables) -/// while the late bound regions are introduced by the function itself. -/// For example, in: -/// ```text -/// impl<'a> Foo<'a> { -/// fn bar<'b>(...) -> ... { ... } -/// } -/// ``` -/// `'a` is early-bound while `'b` is late-bound. -/// - we can't just use `liberate_late_bound_regions`, because we want to know -/// in which *order* the regions were bound, while it is unclear that the -/// b-tree returned by `liberate_late_bound_regions` preserves this order. -/// Also note that this is just a matter of stability of the translation. -/// For instance, when generating function translations, we have to generate -/// one backward function per region, and we need to know in which order to -/// introduce those backward functions. -pub fn replace_late_bound_regions<'tcx, T>( - tcx: TyCtxt<'tcx>, - value: rustc_middle::ty::Binder<'tcx, T>, - def_id: DefId, -) -> (T, LinkedHashMap>) -where - T: rustc_middle::ty::TypeFoldable>, -{ - // Instantiate the regions bound in the signature, and generate a mapping - // while doing so (the mapping uses a linked hash map so that we remember - // in which order we introduced the regions). - // Note that replace_late_bound_regions` returns a map from bound regions to - // regions, but it is unclear whether this map preserves the order in which - // the regions were introduced (the map is a BTreeMap, so I guess it depends - // on how the the bound variables were numbered) and it doesn't cost us - // much to create this mapping ourselves. - let mut late_bound_regions: LinkedHashMap = LinkedHashMap::new(); - let (value, _) = tcx.replace_late_bound_regions(value, |br| { - let nregion = Region::new_from_kind( - tcx, - RegionKind::ReFree(FreeRegion { - scope: def_id, - bound_region: br.kind, - }), - ); - late_bound_regions.insert(br, nregion); - nregion - }); - (value, late_bound_regions) -} - -/// Function used for sanity checks: check the constraints given by a definition's -/// generics (lifetime constraints, traits, etc.). -/// For now we simply check that there are no such constraints... -fn check_generics(tcx: TyCtxt<'_>, def_id: DefId) { - // Retrieve the generics and the predicates (where-clauses) - let _generics = tcx.generics_of(def_id); - let preds = tcx.predicates_of(def_id); - - // For now, simply check that there are no where-clauses - trace!("{:?}", def_id); - trace!("{:?}", &preds.predicates); - for (pred, _span) in preds.predicates { - // Instantiate the predicate (it is wrapped in a binder: we need to - // instantiate the bound region variables with free variables). - let (pred_kind, _late_bound_regions) = replace_late_bound_regions(tcx, pred.kind(), def_id); - match pred_kind { - PredicateKind::Clause(Clause::Trait(trait_pred)) => { - // Slightly annoying: some traits are implicit. - // - // For instance, whenever we use a type parameter in a definition, - // Rust implicitly considers it as implementing trait `std::marker::Sized`. - // For now, we check that there are only instances of this trait, - // and ignore it. - use rustc_middle::ty::{BoundConstness, ImplPolarity}; - assert!(trait_pred.polarity == ImplPolarity::Positive); - // Note sure what this is about - assert!(trait_pred.constness == BoundConstness::NotConst); - let trait_name = trait_def_id_to_name(tcx, trait_pred.trait_ref.def_id); - trace!("{}", trait_name); - assert!( - trait_name.equals_ref_name(&assumed::MARKER_SIZED_NAME), - "Unsupported trait: {:?}", - trait_name - ); - } - PredicateKind::Clause(Clause::RegionOutlives(_)) => unimplemented!(), - PredicateKind::Clause(Clause::TypeOutlives(_)) => unimplemented!(), - PredicateKind::Clause(Clause::Projection(_)) => unimplemented!(), - PredicateKind::Clause(Clause::ConstArgHasType(..)) => { - // I don't really understand that one - } - PredicateKind::WellFormed(_) => unimplemented!(), - PredicateKind::ObjectSafe(_) => unimplemented!(), - PredicateKind::ClosureKind(_, _, _) => unimplemented!(), - PredicateKind::Subtype(_) => unimplemented!(), - PredicateKind::Coerce(_) => unimplemented!(), - PredicateKind::ConstEvaluatable(_) => unimplemented!(), - PredicateKind::ConstEquate(_, _) => unimplemented!(), - PredicateKind::TypeWellFormedFromEnv(_) => unimplemented!(), - PredicateKind::Ambiguous => unimplemented!(), - PredicateKind::AliasRelate(..) => unimplemented!(), - } - } -} - -/// Check a function's generics -pub(crate) fn check_function_generics(tcx: TyCtxt<'_>, def_id: DefId) { - check_generics(tcx, def_id) -} - -/// Check a type's generics -pub(crate) fn check_type_generics(tcx: TyCtxt<'_>, def_id: DefId) { - check_generics(tcx, def_id) -} - -/// Check a global's generics (to refuse them except Sized trait) -pub(crate) fn check_global_generics(tcx: TyCtxt<'_>, def_id: DefId) { - assert!(tcx.generics_of(def_id).params.is_empty()); - check_generics(tcx, def_id) -} diff --git a/charon/src/get_mir.rs b/charon/src/get_mir.rs index 0f29d49c..fc2c7001 100644 --- a/charon/src/get_mir.rs +++ b/charon/src/get_mir.rs @@ -1,11 +1,9 @@ //! Various utilities to load MIR. //! Allow to easily load the MIR code generated by a specific pass. -#![allow(dead_code)] use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::mir::Body; use rustc_middle::ty::TyCtxt; -use std::cell::Ref; /// TODO: maybe we should always target MIR Built, this would make things /// simpler. In particular, the MIR optimized is very low level and @@ -46,24 +44,27 @@ pub fn get_mir_for_def_id_and_level( tcx: TyCtxt<'_>, def_id: LocalDefId, level: MirLevel, -) -> &Body<'_> { +) -> Body<'_> { + // Below: we **clone** the bodies to make sure we don't have issues with + // locked values (we had in the past). match level { MirLevel::Built => { let body = tcx.mir_built(def_id); - // Rk.: leak is unstable - Ref::leak(body.borrow()) + // We clone to be sure there are no problems with locked values + body.borrow().clone() } MirLevel::Promoted => { let (body, _) = tcx.mir_promoted(def_id); - // Rk.: leak is unstable - Ref::leak(body.borrow()) + // We clone to be sure there are no problems with locked values + body.borrow().clone() } MirLevel::Optimized => { let def_id = DefId { krate: rustc_hir::def_id::LOCAL_CRATE, index: def_id.local_def_index, }; - tcx.optimized_mir(def_id) + // We clone to be sure there are no problems with locked values + tcx.optimized_mir(def_id).clone() } } } diff --git a/charon/src/graphs.rs b/charon/src/graphs.rs index 7020ee9e..be21fb10 100644 --- a/charon/src/graphs.rs +++ b/charon/src/graphs.rs @@ -123,7 +123,7 @@ pub fn reorder_sccs( sccs.iter().for_each(|_| reordered_sccs.push(vec![])); for id in ids { let scc_id = match id_to_scc.get(id) { - None => panic!("Could not find id; {:?}", id), + None => panic!("Could not find id: {:?}", id), Some(id) => id, }; reordered_sccs[*scc_id].push(*id); diff --git a/charon/src/id_map.rs b/charon/src/id_map.rs index 0385ed49..758903c6 100644 --- a/charon/src/id_map.rs +++ b/charon/src/id_map.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] //! A map with custom index types. //! //! This data-structure is mostly meant to be used with the index types defined @@ -10,12 +9,14 @@ pub use std::collections::btree_map::IterMut as IterAllMut; pub use std::collections::BTreeMap; use std::iter::{FromIterator, IntoIterator}; +#[derive(Clone)] pub struct Map { - // We use a btree map so that the bindings are sorted by key + // We use an ord map so that the bindings are sorted by key pub map: std::collections::BTreeMap, } impl Map { + #[allow(clippy::new_without_default)] pub fn new() -> Self { Map { map: std::collections::BTreeMap::new(), @@ -35,7 +36,7 @@ impl Map { } pub fn iter(&self) -> impl Iterator { - self.map.iter().map(|(_, x)| x) + self.map.values() } pub fn iter_mut(&mut self) -> impl Iterator { @@ -49,6 +50,10 @@ impl Map { pub fn len(&self) -> usize { self.map.len() } + + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } } impl<'a, Id, T> IntoIterator for &'a Map diff --git a/charon/src/id_vector.rs b/charon/src/id_vector.rs index e8948bcd..0381c3f2 100644 --- a/charon/src/id_vector.rs +++ b/charon/src/id_vector.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] //! A vector with custom index types. //! //! This data-structure is mostly meant to be used with the index types defined @@ -7,6 +6,8 @@ //! //! Note that this data structure is implemented by using persistent vectors. //! This makes the clone operation almost a no-op. +//! +//! TODO: Rustc already provides an `index_vector`. Use it? use serde::{Serialize, Serializer}; use std::iter::{FromIterator, IntoIterator}; @@ -33,7 +34,7 @@ where I: ToUsize, T: Clone, { - vector: im::Vector, + pub vector: im::Vector, /// This is a bit annoying, but we need to use `I` somewhere. phantom: std::marker::PhantomData, } diff --git a/charon/src/index_to_function_calls.rs b/charon/src/index_to_function_calls.rs index 6223ea50..1451ec48 100644 --- a/charon/src/index_to_function_calls.rs +++ b/charon/src/index_to_function_calls.rs @@ -1,15 +1,12 @@ //! Desugar array/slice index operations to function calls. -#![allow(dead_code)] - use crate::expressions::{BorrowKind, MutExprVisitor, Operand, Place, ProjectionElem, Rvalue}; -use crate::gast::{Call, Var}; -use crate::llbc_ast::{ - iter_function_bodies, iter_global_bodies, AssumedFunId, CtxNames, FunDecls, FunId, GlobalDecls, - MutAstVisitor, RawStatement, Statement, Switch, -}; +use crate::formatter::Formatter; +use crate::gast::{Call, GenericArgs, Var}; +use crate::llbc_ast::*; use crate::meta::Meta; -use crate::types::{AssumedTy, ConstGeneric, ErasedRegion, MutTypeVisitor, RefKind, Ty}; +use crate::translate_ctx::TransCtx; +use crate::types::*; use crate::values::VarId; use std::mem::replace; @@ -37,8 +34,8 @@ impl<'a> Transform<'a> { if pe.is_index() { let (index_var_id, buf_ty) = pe.to_index(); - let (id, _, tys, cgs) = buf_ty.as_adt(); - let cgs: Vec = cgs.iter().cloned().collect(); + let (id, generics) = buf_ty.as_adt(); + let cgs: Vec = generics.const_generics.to_vec(); let index_id = match id.as_assumed() { AssumedTy::Array => { if mut_access { @@ -57,7 +54,7 @@ impl<'a> Transform<'a> { _ => unreachable!(), }; - let elem_ty = tys[0].clone(); + let elem_ty = generics.types[0].clone(); // We need to introduce intermediate statements (and // temporary variables) @@ -69,7 +66,7 @@ impl<'a> Transform<'a> { // Push the statement: //`tmp0 = & proj` - let buf_borrow_ty = Ty::Ref(ErasedRegion::Erased, Box::new(buf_ty), ref_kind); + let buf_borrow_ty = Ty::Ref(Region::Erased, Box::new(buf_ty), ref_kind); let buf_borrow_var = self.locals.fresh_var(Option::None, buf_borrow_ty); let borrow_st = RawStatement::Assign( Place::new(buf_borrow_var), @@ -89,18 +86,20 @@ impl<'a> Transform<'a> { // Push the statement: // `tmp1 = Array{Mut,Shared}Index(move tmp0, copy i)` - let elem_borrow_ty = - Ty::Ref(ErasedRegion::Erased, Box::new(elem_ty.clone()), ref_kind); + let elem_borrow_ty = Ty::Ref(Region::Erased, Box::new(elem_ty.clone()), ref_kind); let elem_borrow_var = self.locals.fresh_var(Option::None, elem_borrow_ty); let arg_buf = Operand::Move(Place::new(buf_borrow_var)); let arg_index = Operand::Copy(Place::new(index_var_id)); let index_dest = Place::new(elem_borrow_var); - let index_id = FunId::Assumed(index_id); - let index_call = Call { + let index_id = FunIdOrTraitMethodRef::mk_assumed(index_id); + let generics = GenericArgs::new(vec![Region::Erased], vec![elem_ty], cgs, vec![]); + let func = FnPtr { func: index_id, - region_args: vec![ErasedRegion::Erased], - type_args: vec![elem_ty], - const_generic_args: cgs, + generics, + trait_and_method_generic_args: None, + }; + let index_call = Call { + func, args: vec![arg_buf, arg_index], dest: index_dest, }; @@ -148,7 +147,7 @@ impl<'a> MutExprVisitor for Transform<'a> { fn visit_rvalue(&mut self, rv: &mut Rvalue) { use Rvalue::*; match rv { - Use(_) | UnaryOp(..) | BinaryOp(..) | Aggregate(..) | Global(..) => { + Use(_) | UnaryOp(..) | BinaryOp(..) | Aggregate(..) | Global(..) | Repeat(..) => { // We don't access places here, only operands self.default_visit_rvalue(rv) } @@ -174,6 +173,7 @@ impl<'a> MutExprVisitor for Transform<'a> { impl<'a> MutAstVisitor for Transform<'a> { fn spawn(&mut self, visitor: &mut dyn FnMut(&mut Self)) { + #[allow(clippy::mem_replace_with_default)] let statements = replace(&mut self.statements, Vec::new()); visitor(self); // Make sure we didn't update the vector of statements @@ -219,7 +219,7 @@ impl<'a> MutAstVisitor for Transform<'a> { } } -fn transform_st(locals: &mut VarId::Vector, s: &mut Statement) -> Vec { +fn transform_st(locals: &mut VarId::Vector, s: &mut Statement) -> Option> { // Explore the places/operands let mut visitor = Transform { locals, @@ -229,7 +229,7 @@ fn transform_st(locals: &mut VarId::Vector, s: &mut Statement) -> Vec, s: &mut Statement) -> Vec, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to transform array/slice index operations to function calls: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to transform array/slice index operations to function calls: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); let body = &mut b.body; let locals = &mut b.locals; @@ -302,8 +303,9 @@ pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut Glob let mut tr = |s: &mut Statement| transform_st(locals, s); body.transform(&mut tr); trace!( - "# After transforming array/slice index operations to function calls: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# After transforming array/slice index operations to function calls: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); } } diff --git a/charon/src/insert_assign_return_unit.rs b/charon/src/insert_assign_return_unit.rs index f6dcf83d..dd87852d 100644 --- a/charon/src/insert_assign_return_unit.rs +++ b/charon/src/insert_assign_return_unit.rs @@ -3,92 +3,57 @@ //! of AENEAS, it means the return variable contains ⊥ upon returning. //! For this reason, when the function has return type unit, we insert //! an extra assignment just before returning. -use take_mut::take; use crate::expressions::*; +use crate::formatter::Formatter; use crate::llbc_ast::{ - CtxNames, ExprBody, FunDecl, FunDecls, GlobalDecl, GlobalDecls, RawStatement, Statement, Switch, + ExprBody, FunDecl, FunDecls, GlobalDecl, GlobalDecls, RawStatement, Statement, }; use crate::names::Name; +use crate::translate_ctx::TransCtx; +use crate::types::*; use crate::values::*; -use std::iter::FromIterator; -fn transform_st(mut st: Statement) -> Statement { - st.content = match st.content { - RawStatement::Return => { - // The interesting case - let ret_place = Place { - var_id: VarId::Id::new(0), - projection: Projection::new(), - }; - let unit_value = Rvalue::Aggregate(AggregateKind::Tuple, Vec::new()); - let assign_st = Statement::new(st.meta, RawStatement::Assign(ret_place, unit_value)); - let ret_st = Statement::new(st.meta, RawStatement::Return); - RawStatement::Sequence(Box::new(assign_st), Box::new(ret_st)) - } - RawStatement::Assign(p, rv) => RawStatement::Assign(p, rv), - RawStatement::FakeRead(p) => RawStatement::FakeRead(p), - RawStatement::SetDiscriminant(p, vid) => RawStatement::SetDiscriminant(p, vid), - RawStatement::Drop(p) => RawStatement::Drop(p), - RawStatement::Assert(assert) => RawStatement::Assert(assert), - RawStatement::Call(call) => RawStatement::Call(call), - RawStatement::Panic => RawStatement::Panic, - RawStatement::Break(i) => RawStatement::Break(i), - RawStatement::Continue(i) => RawStatement::Continue(i), - RawStatement::Nop => RawStatement::Nop, - RawStatement::Switch(switch) => match switch { - Switch::If(op, st1, st2) => { - let st1 = Box::new(transform_st(*st1)); - let st2 = Box::new(transform_st(*st2)); - RawStatement::Switch(Switch::If(op, st1, st2)) - } - Switch::SwitchInt(op, int_ty, targets, mut otherwise) => { - let targets = - Vec::from_iter(targets.into_iter().map(|(v, e)| (v, transform_st(e)))); - *otherwise = transform_st(*otherwise); - let switch = Switch::SwitchInt(op, int_ty, targets, otherwise); - RawStatement::Switch(switch) - } - Switch::Match(op, targets, mut otherwise) => { - let targets = - Vec::from_iter(targets.into_iter().map(|(v, e)| (v, transform_st(e)))); - *otherwise = transform_st(*otherwise); - let switch = Switch::Match(op, targets, otherwise); - RawStatement::Switch(switch) - } - }, - RawStatement::Loop(loop_body) => RawStatement::Loop(Box::new(transform_st(*loop_body))), - RawStatement::Sequence(st1, st2) => { - RawStatement::Sequence(Box::new(transform_st(*st1)), Box::new(transform_st(*st2))) - } +fn transform_st(st: &mut Statement) -> Option> { + if let RawStatement::Return = &mut st.content { + let ret_place = Place { + var_id: VarId::Id::new(0), + projection: Projection::new(), + }; + let unit_value = Rvalue::Aggregate( + AggregateKind::Adt(TypeId::Tuple, None, GenericArgs::empty()), + Vec::new(), + ); + let assign_st = Statement::new(st.meta, RawStatement::Assign(ret_place, unit_value)); + let ret_st = Statement::new(st.meta, RawStatement::Return); + st.content = RawStatement::Sequence(Box::new(assign_st), Box::new(ret_st)); }; - st + None } -fn transform_body(fmt_ctx: &CtxNames<'_>, name: &Name, body: &mut Option) { +fn transform_body(ctx: &TransCtx, name: &Name, body: &mut Option) { if let Some(b) = body.as_mut() { trace!( - "About to insert assign and return unit in decl: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "About to insert assign and return unit in decl: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); - take(&mut b.body, transform_st); + b.body.transform(&mut transform_st); } } -fn transform_function(fmt_ctx: &CtxNames<'_>, def: &mut FunDecl) { +fn transform_function(ctx: &TransCtx, def: &mut FunDecl) { if def.signature.output.is_unit() { - transform_body(fmt_ctx, &def.name, &mut def.body); + transform_body(ctx, &def.name, &mut def.body); } } -fn transform_global(fmt_ctx: &CtxNames<'_>, def: &mut GlobalDecl) { +fn transform_global(ctx: &TransCtx, def: &mut GlobalDecl) { if def.ty.is_unit() { - transform_body(fmt_ctx, &def.name, &mut def.body); + transform_body(ctx, &def.name, &mut def.body); } } -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { - funs.iter_mut().for_each(|d| transform_function(fmt_ctx, d)); - globals - .iter_mut() - .for_each(|d| transform_global(fmt_ctx, d)); +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { + funs.iter_mut().for_each(|d| transform_function(ctx, d)); + globals.iter_mut().for_each(|d| transform_global(ctx, d)); } diff --git a/charon/src/lib.rs b/charon/src/lib.rs index 6c860e57..34020cdd 100644 --- a/charon/src/lib.rs +++ b/charon/src/lib.rs @@ -16,6 +16,9 @@ #![feature(cell_leak)] // For Ref::leak // For rustdoc: prevents overflows #![recursion_limit = "256"] +#![feature(trait_alias)] +#![feature(let_chains)] +#![feature(iterator_try_collect)] extern crate hashlink; extern crate im; @@ -49,11 +52,9 @@ pub mod driver; pub mod export; pub mod expressions; pub mod expressions_utils; -pub mod extract_global_assignments; pub mod formatter; pub mod gast; pub mod gast_utils; -pub mod generics; pub mod get_mir; pub mod graphs; pub mod id_map; @@ -69,17 +70,19 @@ pub mod names; pub mod names_utils; pub mod ops_to_function_calls; pub mod reconstruct_asserts; -pub mod regions_hierarchy; -pub mod regularize_constant_adts; pub mod remove_drop_never; pub mod remove_dynamic_checks; +pub mod remove_nops; pub mod remove_read_discriminant; pub mod remove_unused_locals; pub mod reorder_decls; +pub mod simplify_constants; pub mod translate_constants; pub mod translate_crate_to_ullbc; pub mod translate_ctx; pub mod translate_functions_to_ullbc; +pub mod translate_predicates; +pub mod translate_traits; pub mod translate_types; pub mod types; pub mod types_utils; diff --git a/charon/src/llbc_ast.rs b/charon/src/llbc_ast.rs index 51513a25..25420166 100644 --- a/charon/src/llbc_ast.rs +++ b/charon/src/llbc_ast.rs @@ -6,13 +6,11 @@ //! Also note that we completely break the definitions Statement and Terminator //! from MIR to use Statement only. -#![allow(dead_code)] -use crate::expressions::*; pub use crate::gast::*; pub use crate::llbc_ast_utils::*; use crate::meta::Meta; use crate::types::*; -pub use crate::ullbc_ast::{Call, CtxNames, FunDeclId, GlobalDeclId, Var}; +pub use crate::ullbc_ast::{Call, FunDeclId, GlobalDeclId, Var}; use crate::values::*; use macros::{EnumAsGetters, EnumIsA, EnumToGetters, VariantIndexArity, VariantName}; use serde::Serialize; @@ -68,7 +66,9 @@ pub struct Statement { pub content: RawStatement, } -#[derive(Debug, Clone, EnumIsA, EnumToGetters, EnumAsGetters, VariantName, VariantIndexArity)] +#[derive( + Debug, Clone, EnumIsA, EnumToGetters, EnumAsGetters, Serialize, VariantName, VariantIndexArity, +)] pub enum Switch { /// Gives the `if` block and the `else` block If(Operand, Box, Box), diff --git a/charon/src/llbc_ast_utils.rs b/charon/src/llbc_ast_utils.rs index 042d46a4..225bfb58 100644 --- a/charon/src/llbc_ast_utils.rs +++ b/charon/src/llbc_ast_utils.rs @@ -1,26 +1,16 @@ //! Implementations for [crate::llbc_ast] -#![allow(dead_code)] -use std::ops::DerefMut; - use crate::common::*; use crate::expressions::{MutExprVisitor, Operand, Place, Rvalue}; -use crate::formatter::Formatter; -use crate::llbc_ast::{ - Assert, Call, ExprBody, FunDecl, FunDecls, GlobalDecl, GlobalDecls, RawStatement, Statement, - Switch, -}; +use crate::gast_utils::{ExprFormatter, GFunDeclFormatter, GGlobalDeclFormatter}; +use crate::llbc_ast::{Assert, FunDecl, GlobalDecl, RawStatement, Statement, Switch}; use crate::meta; use crate::meta::Meta; use crate::types::*; -use crate::ullbc_ast::{ - fmt_call, CtxNames, FunDeclId, FunNamesFormatter, FunSigFormatter, GAstFormatter, GlobalDeclId, - GlobalNamesFormatter, TAB_INCR, -}; +pub use crate::ullbc_ast::fmt_call; use crate::values::*; use macros::make_generic_in_borrows; -use serde::ser::SerializeTupleVariant; -use serde::{Serialize, Serializer}; +use std::ops::DerefMut; use take_mut::take; /// Goes from e.g. `(A; B; C) ; D` to `(A; (B; (C; D)))`. @@ -111,68 +101,14 @@ impl Switch { } } -impl Serialize for Switch { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - let enum_name = "Switch"; - let variant_name = self.variant_name(); - let (variant_index, variant_arity) = self.variant_index_arity(); - let mut vs = serializer.serialize_tuple_variant( - enum_name, - variant_index, - variant_name, - variant_arity, - )?; - match self { - Switch::If(op, e1, e2) => { - vs.serialize_field(op)?; - vs.serialize_field(e1)?; - vs.serialize_field(e2)?; - } - Switch::SwitchInt(op, int_ty, targets, otherwise) => { - vs.serialize_field(op)?; - vs.serialize_field(int_ty)?; - let targets: Vec<(VecSerializer, &Statement)> = targets - .iter() - .map(|(values, st)| (VecSerializer::new(values), st)) - .collect(); - let targets = VecSerializer::new(&targets); - vs.serialize_field(&targets)?; - vs.serialize_field(otherwise)?; - } - Switch::Match(p, targets, otherwise) => { - vs.serialize_field(p)?; - let targets: Vec<(VecSerializer, &Statement)> = targets - .iter() - .map(|(values, st)| (VecSerializer::new(values), st)) - .collect(); - let targets = VecSerializer::new(&targets); - vs.serialize_field(&targets)?; - vs.serialize_field(otherwise)?; - } - } - vs.end() - } -} - impl Statement { pub fn new(meta: Meta, content: RawStatement) -> Self { Statement { meta, content } } - pub fn fmt_with_ctx<'a, 'b, 'c, T>(&'a self, tab: &'b str, ctx: &'c T) -> String + pub fn fmt_with_ctx(&self, tab: &str, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + T: ExprFormatter, { match &self.content { RawStatement::Assign(place, rvalue) => format!( @@ -200,16 +136,8 @@ impl Statement { assert.expected, ), RawStatement::Call(call) => { - let Call { - func, - region_args, - type_args, - const_generic_args, - args, - dest, - } = call; - let call = fmt_call(ctx, func, region_args, type_args, const_generic_args, args); - format!("{}{} := {}", tab, dest.fmt_with_ctx(ctx), call) + let (call_s, _) = fmt_call(ctx, call); + format!("{tab}{} := {call_s}", call.dest.fmt_with_ctx(ctx),) } RawStatement::Panic => format!("{tab}panic"), RawStatement::Return => format!("{tab}return"), @@ -294,7 +222,7 @@ impl Statement { let maps = maps.join(",\n"); format!( - "{}map {} {{\n{}\n{}}}", + "{}match {} {{\n{}\n{}}}", tab, discr.fmt_with_ctx(ctx), maps, @@ -315,184 +243,21 @@ impl Statement { } } -pub(crate) struct FunDeclsFormatter<'ctx> { - decls: &'ctx FunDecls, -} - -pub(crate) struct GlobalDeclsFormatter<'ctx> { - decls: &'ctx GlobalDecls, -} - -impl<'ctx> FunDeclsFormatter<'ctx> { - pub fn new(decls: &'ctx FunDecls) -> Self { - FunDeclsFormatter { decls } - } -} - -impl<'ctx> Formatter for FunDeclsFormatter<'ctx> { - fn format_object(&self, id: FunDeclId::Id) -> String { - let d = self.decls.get(id).unwrap(); - d.name.to_string() - } -} - -impl<'ctx> GlobalDeclsFormatter<'ctx> { - pub fn new(decls: &'ctx GlobalDecls) -> Self { - GlobalDeclsFormatter { decls } - } -} - -impl<'ctx> Formatter for GlobalDeclsFormatter<'ctx> { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - let d = self.decls.get(id).unwrap(); - d.name.to_string() - } -} - -impl<'ctx, FD, GD> Formatter<&Statement> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, - Self: Formatter, -{ - fn format_object(&self, st: &Statement) -> String { - st.fmt_with_ctx(TAB_INCR, self) - } -} - -impl ExprBody { - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let locals = Some(&self.locals); - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - // No local types or const generics, both are set to None - let ctx = GAstFormatter::new(ty_ctx, &fun_ctx, &global_ctx, None, locals, None); - self.fmt_with_ctx(TAB_INCR, &ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let locals = Some(&self.locals); - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - let ctx = GAstFormatter::new(ty_ctx, &fun_ctx, &global_ctx, None, locals, None); - self.fmt_with_ctx(TAB_INCR, &ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) - } -} - impl FunDecl { - pub fn fmt_with_ctx<'ctx, FD, GD>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FD, - global_ctx: &'ctx GD, - ) -> String + pub fn fmt_with_ctx<'a, C>(&'a self, ctx: &C) -> String where - FD: Formatter, - GD: Formatter, + C: GFunDeclFormatter<'a, Statement>, { - // Initialize the contexts - let fun_sig_ctx = FunSigFormatter { - ty_ctx, - global_ctx, - sig: &self.signature, - }; - - let locals = self.body.as_ref().map(|body| &body.locals); - let fmt_ctx = GAstFormatter::new( - ty_ctx, - fun_ctx, - global_ctx, - Some(&self.signature.type_params), - locals, - Some(&self.signature.const_generic_params), - ); - - // Use the contexts for printing - self.gfmt_with_ctx("", &fun_sig_ctx, &fmt_ctx) - } - - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) + self.gfmt_with_ctx("", ctx) } } impl GlobalDecl { - pub fn fmt_with_ctx<'ctx, FD, GD>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FD, - global_ctx: &'ctx GD, - ) -> String + pub fn fmt_with_ctx<'a, C>(&'a self, ctx: &C) -> String where - FD: Formatter, - GD: Formatter, + C: GGlobalDeclFormatter<'a, Statement>, { - let locals = self.body.as_ref().map(|body| &body.locals); - let fmt_ctx = GAstFormatter::new(ty_ctx, fun_ctx, global_ctx, None, locals, None); - - // Use the contexts for printing - self.gfmt_with_ctx("", &fmt_ctx) - } - - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) + self.gfmt_with_ctx("", ctx) } } @@ -661,13 +426,22 @@ pub trait AstVisitor: crate::expressions::ExprVisitor { } // make_generic_in_borrows /// Helper for [transform_statements] -struct TransformStatements<'a, F: FnMut(&mut Statement) -> Vec> { +struct TransformStatements<'a, F: FnMut(&mut Statement) -> Option>> { tr: &'a mut F, } -impl<'a, F: FnMut(&mut Statement) -> Vec> MutTypeVisitor for TransformStatements<'a, F> {} -impl<'a, F: FnMut(&mut Statement) -> Vec> MutExprVisitor for TransformStatements<'a, F> {} -impl<'a, F: FnMut(&mut Statement) -> Vec> MutAstVisitor for TransformStatements<'a, F> { +impl<'a, F: FnMut(&mut Statement) -> Option>> MutTypeVisitor + for TransformStatements<'a, F> +{ +} +impl<'a, F: FnMut(&mut Statement) -> Option>> MutExprVisitor + for TransformStatements<'a, F> +{ +} + +impl<'a, F: FnMut(&mut Statement) -> Option>> MutAstVisitor + for TransformStatements<'a, F> +{ fn visit_statement(&mut self, st: &mut Statement) { match &mut st.content { RawStatement::Sequence(st1, st2) => { @@ -677,9 +451,10 @@ impl<'a, F: FnMut(&mut Statement) -> Vec> MutAstVisitor for Transform // Transform the current statement let st_seq = (self.tr)(st1); - if !st_seq.is_empty() { - take(st, |st| chain_statements(st_seq, st)) + if let Some(seq) = st_seq && !seq.is_empty() { + take(st, |st| chain_statements(seq, st)) } + // TODO: we might want to apply tr to the whole sequence } _ => { // Bottom-up @@ -687,8 +462,8 @@ impl<'a, F: FnMut(&mut Statement) -> Vec> MutAstVisitor for Transform // Transform the current statement let st_seq = (self.tr)(st); - if !st_seq.is_empty() { - take(st, |st| chain_statements(st_seq, st)) + if let Some(seq) = st_seq && !seq.is_empty() { + take(st, |st| chain_statements(seq, st)) } } } @@ -713,7 +488,7 @@ impl Statement { /// if in `s1; s2` we transform `s1` to the sequence `s1_1; s1_2`, /// then the resulting statement is `s1_1; s1_2; s2` and **not** /// `{ s1_1; s1_2 }; s2`. - pub fn transform Vec>(&mut self, f: &mut F) { + pub fn transform Option>>(&mut self, f: &mut F) { let mut visitor = TransformStatements { tr: f }; visitor.visit_statement(self); } diff --git a/charon/src/llbc_export.rs b/charon/src/llbc_export.rs index 19bf5d7f..9101cdf5 100644 --- a/charon/src/llbc_export.rs +++ b/charon/src/llbc_export.rs @@ -20,7 +20,7 @@ pub fn export( fun_defs: &FunDecls, global_defs: &GlobalDecls, dest_dir: &Option, -) -> Result<()> { +) -> Result<(),()> { // Generate the destination file - we use the crate name for the file name let mut target_filename = dest_dir .as_deref() diff --git a/charon/src/logger.rs b/charon/src/logger.rs index c94e1d9d..b506f21b 100644 --- a/charon/src/logger.rs +++ b/charon/src/logger.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - extern crate env_logger; /// Initialize the logger. We use a custom initialization to add some diff --git a/charon/src/meta.rs b/charon/src/meta.rs index 06945e71..a716af09 100644 --- a/charon/src/meta.rs +++ b/charon/src/meta.rs @@ -3,7 +3,6 @@ pub use crate::meta_utils::*; use macros::{generate_index_type, EnumAsGetters, EnumIsA}; use serde::Serialize; -use std::path::PathBuf; generate_index_type!(LocalFileId); generate_index_type!(VirtualFileId); @@ -71,9 +70,9 @@ pub struct FileInfo {} #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Serialize)] pub enum FileName { /// A remapped path (namely paths into stdlib) - Virtual(PathBuf), + Virtual(String), /// A local path (a file coming from the current crate for instance) - Local(PathBuf), + Local(String), /// A "not real" file name (macro, query, etc.) NotReal(String), } diff --git a/charon/src/meta_utils.rs b/charon/src/meta_utils.rs index 204a2cd4..24599a93 100644 --- a/charon/src/meta_utils.rs +++ b/charon/src/meta_utils.rs @@ -1,7 +1,6 @@ //! This file groups everything which is linked to implementations about [crate::meta] -#![allow(dead_code)] - use crate::meta::*; +use hax_frontend_exporter as hax; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; use rustc_session::Session; @@ -44,19 +43,24 @@ impl Loc { /// meta-information of, say, a sequence). pub fn combine_meta(m0: &Meta, m1: &Meta) -> Meta { // Merge the spans - assert!(m0.span.file_id == m1.span.file_id); - let span = Span { - file_id: m0.span.file_id, - beg: Loc::min(&m0.span.beg, &m1.span.beg), - end: Loc::max(&m0.span.end, &m1.span.end), - }; - - // We don't attempt to merge the "generated from" spans: they might - // come from different files, and even if they come from the same files - // they might come from different macros, etc. - Meta { - span, - generated_from_span: None, + if m0.span.file_id == m1.span.file_id { + let span = Span { + file_id: m0.span.file_id, + beg: Loc::min(&m0.span.beg, &m1.span.beg), + end: Loc::max(&m0.span.end, &m1.span.end), + }; + + // We don't attempt to merge the "generated from" spans: they might + // come from different files, and even if they come from the same files + // they might come from different macros, etc. + Meta { + span, + generated_from_span: None, + } + } else { + // It happens that the spans don't come from the same file. In this + // situation, we just return the first span. TODO: improve this. + *m0 } } @@ -71,12 +75,13 @@ pub fn combine_meta_iter<'a, T: Iterator>(mut ms: T) -> Meta { mc } -pub fn convert_filename(name: &rustc_span::FileName) -> FileName { +pub fn convert_filename(name: &hax::FileName) -> FileName { match name { - rustc_span::FileName::Real(name) => { + hax::FileName::Real(name) => { + use hax::RealFileName; match name { - rustc_span::RealFileName::LocalPath(path) => FileName::Local(path.clone()), - rustc_span::RealFileName::Remapped { + RealFileName::LocalPath(path) => FileName::Local(path.clone()), + RealFileName::Remapped { local_path: _, virtual_name, } => @@ -86,15 +91,15 @@ pub fn convert_filename(name: &rustc_span::FileName) -> FileName { } } } - rustc_span::FileName::QuoteExpansion(_) - | rustc_span::FileName::Anon(_) - | rustc_span::FileName::MacroExpansion(_) - | rustc_span::FileName::ProcMacroSourceCode(_) - | rustc_span::FileName::CfgSpec(_) - | rustc_span::FileName::CliCrateAttr(_) - | rustc_span::FileName::Custom(_) - | rustc_span::FileName::DocTest(_, _) - | rustc_span::FileName::InlineAsm(_) => { + hax::FileName::QuoteExpansion(_) + | hax::FileName::Anon(_) + | hax::FileName::MacroExpansion(_) + | hax::FileName::ProcMacroSourceCode(_) + | hax::FileName::CfgSpec(_) + | hax::FileName::CliCrateAttr(_) + | hax::FileName::Custom(_) + | hax::FileName::DocTest(..) + | hax::FileName::InlineAsm(_) => { // We use the debug formatter to generate a filename. // This is not ideal, but filenames are for debugging anyway. FileName::NotReal(format!("{name:?}")) @@ -102,23 +107,10 @@ pub fn convert_filename(name: &rustc_span::FileName) -> FileName { } } -/// Return the filename from a Rust span. -pub fn get_filename_from_rspan(sess: &Session, span: rustc_span::Span) -> FileName { - // Retrieve the source map, which contains information about the source file: - // we need it to be able to interpret the span. - let source_map = sess.source_map(); - - // Retrieve the filename - let name = source_map.span_to_filename(span); - - // Convert it - convert_filename(&name) -} - -pub fn convert_loc(loc: rustc_span::Loc) -> Loc { +pub fn convert_loc(loc: hax::Loc) -> Loc { Loc { line: loc.line, - col: loc.col.0, + col: loc.col, } } diff --git a/charon/src/names.rs b/charon/src/names.rs index 456b1872..1dd5cb65 100644 --- a/charon/src/names.rs +++ b/charon/src/names.rs @@ -1,18 +1,25 @@ //! Defines some utilities for the variables -#![allow(dead_code)] - pub use crate::names_utils::*; +use crate::types::*; use macros::generate_index_type; -use macros::EnumIsA; +use macros::{EnumAsGetters, EnumIsA}; use serde::Serialize; generate_index_type!(Disambiguator); /// See the comments for [Name] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, EnumIsA)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, EnumIsA, EnumAsGetters)] pub enum PathElem { - Ident(String), - Disambiguator(Disambiguator::Id), + Ident(String, Disambiguator::Id), + Impl(ImplElem), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ImplElem { + pub generics: GenericParams, + pub preds: Predicates, + pub ty: Ty, + pub disambiguator: Disambiguator::Id, } /// An item name/path @@ -54,10 +61,3 @@ pub enum PathElem { pub struct Name { pub name: Vec, } - -pub type ModuleName = Name; -pub type TypeName = Name; -pub type ItemName = Name; -pub type FunName = Name; -pub type GlobalName = Name; -pub type HirItemName = Name; diff --git a/charon/src/names_utils.rs b/charon/src/names_utils.rs index 8190aa68..3e2f6e26 100644 --- a/charon/src/names_utils.rs +++ b/charon/src/names_utils.rs @@ -2,47 +2,147 @@ //! //! For now, we have one function per object kind (type, trait, function, //! module): many of them could be factorized (will do). -#![allow(dead_code)] - +use crate::formatter::Formatter; +use crate::gast::*; use crate::names::*; -use rustc_hir::def_id::DefId; -use rustc_hir::definitions::DefPathData; +use crate::translate_ctx::*; +use crate::types::*; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; use rustc_hir::{Item, ItemKind}; -use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::DefId; use serde::{Serialize, Serializer}; use std::collections::HashSet; impl PathElem { - // TODO: we could make that an eq trait? - // On the other hand I'm not fond of overloading... fn equals_ident(&self, id: &str) -> bool { match self { - PathElem::Ident(s) => s == id, - PathElem::Disambiguator(_) => false, + PathElem::Ident(s, d) => s == id && d.is_zero(), + PathElem::Impl(_) => false, } } -} -impl std::fmt::Display for PathElem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { match self { - PathElem::Ident(s) => { - write!(f, "{s}") - } - PathElem::Disambiguator(d) => { - write!(f, "{d}") + PathElem::Ident(s, d) => { + let d = if d.is_zero() { + "".to_string() + } else { + format!("#{}", d) + }; + format!("{s}{d}") } + PathElem::Impl(impl_elem) => impl_elem.fmt_with_ctx(ctx), } } } -impl Name { - pub fn from(name: Vec) -> Name { - Name { - name: name.into_iter().map(PathElem::Ident).collect(), +impl ImplElem { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let d = if self.disambiguator.is_zero() { + "".to_string() + } else { + format!("#{}", self.disambiguator) + }; + let fmt = WithGenericsFmt { + ctx, + generics: &self.generics, + }; + // Just printing the generics (not the predicates) + // TODO: there is something wrong here: we should add the generic parameters + // to the context, and then use them to print. + format!("{{{}{d}}}", self.ty.fmt_with_ctx(&fmt),) + } +} + +struct WithGenericsFmt<'a, C> { + ctx: &'a C, + generics: &'a GenericParams, +} + +impl<'a, C> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: TypeVarId::Id) -> String { + self.generics.types.get(x).unwrap().name.to_string() + } +} + +impl<'a, C> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: ConstGenericVarId::Id) -> String { + self.generics + .const_generics + .get(x) + .unwrap() + .name + .to_string() + } +} + +impl<'a, C> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: RegionId::Id) -> String { + match &self.generics.regions.get(x).unwrap().name { + None => "'_".to_string(), + Some(r) => r.to_string(), } } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: TypeDeclId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: FunDeclId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: GlobalDeclId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: TraitDeclId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: TraitImplId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl<'a, C: Formatter> Formatter for WithGenericsFmt<'a, C> { + fn format_object(&self, x: TraitClauseId::Id) -> String { + self.ctx.format_object(x) + } +} + +impl Name { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let name = self + .name + .iter() + .map(|x| x.fmt_with_ctx(ctx)) + .collect::>(); + name.join("::") + } +} +impl Name { #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.name.len() @@ -89,7 +189,9 @@ impl Name { pub fn is_in_modules(&self, krate: &String, modules: &HashSet) -> bool { if self.len() >= 2 { match (&self.name[0], &self.name[1]) { - (PathElem::Ident(s0), PathElem::Ident(s1)) => s0 == krate && modules.contains(s1), + (PathElem::Ident(s0, _), PathElem::Ident(s1, _)) => { + s0 == krate && modules.contains(s1) + } _ => false, } } else { @@ -98,253 +200,241 @@ impl Name { } } -impl std::fmt::Display for Name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let v: Vec = self.name.iter().map(|s| s.to_string()).collect(); - write!(f, "{}", v.join("::")) - } -} - +// Implementating the serializer for Name so as to ignore the wrapper impl Serialize for Name { fn serialize(&self, serializer: S) -> std::result::Result where S: Serializer, { - use crate::common::*; - let name = VecSerializer::new(&self.name); - name.serialize(serializer) + self.name.serialize(serializer) } } -/// Retrieve an item name from a `DefId`. -pub fn item_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> ItemName { - trace!("{:?}", def_id); - - // We have to be a bit careful when retrieving names from def ids. For instance, - // due to reexports, [`TyCtxt::def_path_str`](TyCtxt::def_path_str) might give - // different names depending on the def id on which it is called, even though - // those def ids might actually identify the same definition. - // For instance: `std::boxed::Box` and `alloc::boxed::Box` are actually - // the same (the first one is a reexport). - // This is why we implement a custom function to retrieve the original name - // (though this makes us loose aliases - we may want to investigate this - // issue in the future). - - // We lookup the path associated to an id, and convert it to a name. - // Paths very precisely identify where an item is. There are important - // subcases, like the items in an `Impl` block: - // ``` - // impl List { - // fn new() ... - // } - // ``` - // - // One issue here is that "List" *doesn't appear* in the path, which would - // look like the following: - // - // `TypeNS("Crate") :: Impl :: ValueNs("new")` - // ^^^ - // This is where "List" should be - // - // What we do is the following: - // - we convert the path to a name starting *with the end* - // - whenever we find an "impl" path element, we can actually lookup its - // type (yes, it makes sense for rustc...), which allows us to retrieve - // the type identifier. We then grab its last path element of the type - // identifier (say the identifier is "list::List", we only use "List" - // and insert it in the name). - // - // Besides, as there may be several "impl" blocks for one type, each impl - // block is identified by a unique number (rustc calls this a - // "disambiguator"), which we grab. - // - // Example: - // ======== - // For instance, if we write the following code in crate `test` and module - // `bla`: - // ``` - // impl Foo { - // fn foo() { ... } - // } - // - // impl Foo { - // fn bar() { ... } - // } - // ``` - // - // The names we will generate for `foo` and `bar` are: - // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(0), Ident("foo")]` - // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(1), Ident("bar")]` - let mut found_crate_name = false; - let mut id = def_id; - let mut name: Vec = Vec::new(); - - // Rk.: below we try to be as tight as possible with regards to sanity - // checks, to make sure we understand what happens with def paths, and - // fail whenever we get something which is even slightly outside what - // we expect. - loop { - // Retrieve the id key - let id_key = tcx.def_key(id); - - // Match over the key data - let data = id_key.disambiguated_data; - match data.data { - DefPathData::TypeNs(symbol) => { - assert!(data.disambiguator == 0); // Sanity check - name.push(PathElem::Ident(symbol.to_ident_string())); - } - DefPathData::ValueNs(symbol) => { - if data.disambiguator != 0 { - // I don't like that - - // I think this only happens with names introduced by macros - // (though not sure). For instance: - // `betree_main::betree_utils::_#1::{impl#0}::deserialize::{impl#0}` - let s = symbol.to_ident_string(); - assert!(s == "_"); - name.push(PathElem::Ident(s)); - name.push(PathElem::Disambiguator(Disambiguator::Id::new( - data.disambiguator as usize, - ))); - } else { - name.push(PathElem::Ident(symbol.to_ident_string())); +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { + /// Retrieve an item name from a [DefId]. + pub fn extended_def_id_to_name(&mut self, def_id: &hax::ExtendedDefId) -> Name { + trace!("{:?}", def_id); + let span = self.tcx.def_span(def_id.rust_def_id.unwrap()); + + // We have to be a bit careful when retrieving names from def ids. For instance, + // due to reexports, [`TyCtxt::def_path_str`](TyCtxt::def_path_str) might give + // different names depending on the def id on which it is called, even though + // those def ids might actually identify the same definition. + // For instance: `std::boxed::Box` and `alloc::boxed::Box` are actually + // the same (the first one is a reexport). + // This is why we implement a custom function to retrieve the original name + // (though this makes us loose aliases - we may want to investigate this + // issue in the future). + + // We lookup the path associated to an id, and convert it to a name. + // Paths very precisely identify where an item is. There are important + // subcases, like the items in an `Impl` block: + // ``` + // impl List { + // fn new() ... + // } + // ``` + // + // One issue here is that "List" *doesn't appear* in the path, which would + // look like the following: + // + // `TypeNS("Crate") :: Impl :: ValueNs("new")` + // ^^^ + // This is where "List" should be + // + // For this reason, whenever we find an `Impl` path element, we actually + // lookup the type of the sub-path, from which we can derive a name. + // + // Besides, as there may be several "impl" blocks for one type, each impl + // block is identified by a unique number (rustc calls this a + // "disambiguator"), which we grab. + // + // Example: + // ======== + // For instance, if we write the following code in crate `test` and module + // `bla`: + // ``` + // impl Foo { + // fn foo() { ... } + // } + // + // impl Foo { + // fn bar() { ... } + // } + // ``` + // + // The names we will generate for `foo` and `bar` are: + // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(0), Ident("foo")]` + // `[Ident("test"), Ident("bla"), Ident("Foo"), Disambiguator(1), Ident("bar")]` + let mut found_crate_name = false; + let mut name: Vec = Vec::new(); + + // Rk.: below we try to be as tight as possible with regards to sanity + // checks, to make sure we understand what happens with def paths, and + // fail whenever we get something which is even slightly outside what + // we expect. + for data in &def_id.path { + // Match over the key data + let disambiguator = Disambiguator::Id::new(data.disambiguator as usize); + use hax::ExtendedDefPathItem; + match &data.data { + ExtendedDefPathItem::TypeNs(symbol) => { + assert!(data.disambiguator == 0); // Sanity check + name.push(PathElem::Ident(symbol.clone(), disambiguator)); + } + ExtendedDefPathItem::ValueNs(symbol) => { + if data.disambiguator != 0 { + // I don't like that + + // I think this only happens with names introduced by macros + // (though not sure). For instance: + // `betree_main::betree_utils::_#1::{impl#0}::deserialize::{impl#0}` + let s = symbol; + assert!(s == "_"); + name.push(PathElem::Ident(s.clone(), disambiguator)); + } else { + name.push(PathElem::Ident(symbol.clone(), disambiguator)); + } + } + ExtendedDefPathItem::CrateRoot => { + // Sanity check + assert!(data.disambiguator == 0); + + // This should be the beginning of the path + assert!(name.is_empty()); + found_crate_name = true; + name.push(PathElem::Ident(def_id.krate.clone(), disambiguator)); + } + ExtendedDefPathItem::Impl { + id, + substs, + bounds: _, // We actually need to directly interact with Rustc + ty, + } => { + // We need to convert the type, which may contain quantified + // substs and bounds. In order to properly do so, we introduce + // a body translation context. + let id = id.unwrap(); + let mut bt_ctx = BodyTransCtx::new(id, self); + + bt_ctx + .translate_generic_params_from_hax(span, substs) + .unwrap(); + bt_ctx.translate_predicates_of(None, id).unwrap(); + let erase_regions = false; + let ty = bt_ctx.translate_ty(span, erase_regions, ty).unwrap(); + + name.push(PathElem::Impl(ImplElem { + generics: bt_ctx.get_generics(), + preds: bt_ctx.get_predicates(), + ty, + disambiguator, + })); + } + ExtendedDefPathItem::ImplTrait => { + // TODO: do nothing for now + } + ExtendedDefPathItem::MacroNs(symbol) => { + assert!(data.disambiguator == 0); // Sanity check + + // There may be namespace collisions between, say, function + // names and macros (not sure). However, this isn't much + // of an issue here, because for now we don't expose macros + // in the AST, and only use macro names in [register], for + // instance to filter opaque modules. + name.push(PathElem::Ident(symbol.clone(), disambiguator)); + } + ExtendedDefPathItem::ClosureExpr => { + // TODO: this is not very satisfactory, but on the other hand + // we should be able to extract closures in local let-bindings + // (i.e., we shouldn't have to introduce top-level let-bindings). + name.push(PathElem::Ident("closure".to_string(), disambiguator)) + } + _ => { + error!("Unexpected ExtendedDefPathItem: {:?}", data); + unreachable!("Unexpected ExtendedDefPathItem: {:?}", data); } } - DefPathData::CrateRoot => { - // Sanity check - assert!(data.disambiguator == 0); + } - // This should be the end of the path - assert!(id_key.parent.is_none()); - found_crate_name = true; + // We always add the crate name + if !found_crate_name { + name.push(PathElem::Ident( + def_id.krate.clone(), + Disambiguator::Id::new(0), + )); + } - let crate_name = tcx.crate_name(id.krate).to_string(); - name.push(PathElem::Ident(crate_name)); - } - DefPathData::Impl => { - // Push the disambiguator - name.push(PathElem::Disambiguator(Disambiguator::Id::new( - data.disambiguator as usize, - ))); - - // "impl" blocks are defined for types. - // We retrieve its unqualified type name. - let ty = tcx.type_of(id).subst_identity(); - - // Match over the type. - name.push(PathElem::Ident(match ty.kind() { - rustc_middle::ty::TyKind::Adt(adt_def, _) => { - let mut type_name = type_def_id_to_name(tcx, adt_def.did()); - type_name.name.pop().unwrap().to_string() - } - // Builtin cases. - rustc_middle::ty::TyKind::Int(_) - | rustc_middle::ty::TyKind::Uint(_) - | rustc_middle::ty::TyKind::Array(..) - | rustc_middle::ty::TyKind::Slice(_) => { - format!("{ty:?}") - } - _ => unreachable!(), - })); - } - DefPathData::ImplTrait => { - // TODO: this should work the same as for `Impl` - unimplemented!(); + trace!("{:?}", name); + Name { name } + } + + pub(crate) fn make_hax_state_with_id( + &mut self, + def_id: DefId, + ) -> hax::State, (), (), DefId> { + hax::state::State { + thir: (), + mir: (), + owner_id: def_id, + base: hax::Base::new( + self.tcx, + hax::options::Options { + inline_macro_calls: Vec::new(), + }, + ), + } + } + + /// Returns an optional name for an HIR item. + /// + /// If the option is `None`, it means the item is to be ignored (example: it + /// is a `use` item). + /// + /// Rk.: this function is only used by [crate::register], and implemented with this + /// context in mind. + pub fn hir_item_to_name(&mut self, item: &Item) -> Option { + // We have to create a hax state, which is annoying... + let state = self.make_hax_state_with_id(item.owner_id.to_def_id()); + let def_id = item.owner_id.to_def_id().sinto(&state); + + match &item.kind { + ItemKind::OpaqueTy(_) => unimplemented!(), + ItemKind::Union(_, _) => unimplemented!(), + ItemKind::ExternCrate(_) => { + // We ignore this - + // TODO: investigate when extern crates appear, and why + Option::None } - DefPathData::MacroNs(symbol) => { - assert!(data.disambiguator == 0); // Sanity check - - // There may be namespace collisions between, say, function - // names and macros (not sure). However, this isn't much - // of an issue here, because for now we don't expose macros - // in the AST, and only use macro names in [register], for - // instance to filter opaque modules. - name.push(PathElem::Ident(symbol.to_ident_string())); + ItemKind::Use(_, _) => Option::None, + ItemKind::TyAlias(_, _) => { + // We ignore the type aliases - it seems they are inlined + Option::None } + ItemKind::Enum(_, _) + | ItemKind::Struct(_, _) + | ItemKind::Fn(_, _, _) + | ItemKind::Impl(_) + | ItemKind::Mod(_) + | ItemKind::Const(_, _) + | ItemKind::Static(_, _, _) + | ItemKind::Macro(_, _) + | ItemKind::Trait(..) => Option::Some(self.extended_def_id_to_name(&def_id)), _ => { - error!("Unexpected DefPathData: {:?}", data); - unreachable!("Unexpected DefPathData: {:?}", data); - } - } - - // Update the id to be the parent id - match id_key.parent { - Some(parent_index) => id.index = parent_index, - None => { - // We completely explored the path - break; + unimplemented!("{:?}", item.kind); } } } - // We always add the crate name - if !found_crate_name { - let crate_name = tcx.crate_name(id.krate).to_string(); - name.push(PathElem::Ident(crate_name)); + // TODO: remove + pub fn item_def_id_to_name(&mut self, def_id: rustc_span::def_id::DefId) -> Name { + let state = self.make_hax_state_with_id(def_id); + self.extended_def_id_to_name(&def_id.sinto(&state)) } - // Reverse the name and return - name.reverse(); - trace!("{:?}", name); - Name { name } -} - -pub fn type_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> TypeName { - item_def_id_to_name(tcx, def_id) -} - -pub fn module_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> ModuleName { - item_def_id_to_name(tcx, def_id) -} - -pub fn function_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> FunName { - item_def_id_to_name(tcx, def_id) -} - -pub fn global_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> GlobalName { - item_def_id_to_name(tcx, def_id) -} - -pub fn trait_def_id_to_name(tcx: TyCtxt, def_id: DefId) -> FunName { - item_def_id_to_name(tcx, def_id) -} - -/// Returns an optional name for an HIR item. -/// -/// If the option is `None`, it means the item is to be ignored (example: it -/// is a `use` item). -/// -/// Rk.: this function is only used by [crate::register], and implemented with this -/// context in mind. -pub fn hir_item_to_name(tcx: TyCtxt, item: &Item) -> Option { - let def_id = item.owner_id.to_def_id(); - - // TODO: calling different functions to retrieve the name is not very - // satisfying below - match &item.kind { - ItemKind::OpaqueTy(_) => unimplemented!(), - ItemKind::Union(_, _) => unimplemented!(), - ItemKind::ExternCrate(_) => { - // We ignore this - - // TODO: investigate when extern crates appear, and why - Option::None - } - ItemKind::Use(_, _) => Option::None, - ItemKind::TyAlias(_, _) => { - // We ignore the type aliases - it seems they are inlined - Option::None - } - ItemKind::Enum(_, _) - | ItemKind::Struct(_, _) - | ItemKind::Fn(_, _, _) - | ItemKind::Impl(_) - | ItemKind::Mod(_) - | ItemKind::Const(_, _) - | ItemKind::Static(_, _, _) - | ItemKind::Macro(_, _) => Option::Some(item_def_id_to_name(tcx, def_id)), - _ => { - unimplemented!("{:?}", item.kind); - } + pub fn def_id_to_name(&mut self, def_id: &hax::DefId) -> Name { + // We have to create a hax state, which is annoying... + let state = self.make_hax_state_with_id(def_id.rust_def_id.unwrap()); + self.extended_def_id_to_name(&def_id.rust_def_id.unwrap().sinto(&state)) } } diff --git a/charon/src/ops_to_function_calls.rs b/charon/src/ops_to_function_calls.rs index d0390f02..59bf8e17 100644 --- a/charon/src/ops_to_function_calls.rs +++ b/charon/src/ops_to_function_calls.rs @@ -1,19 +1,14 @@ -//! Desugar some unary/binary operations to function calls. +//! Desugar some unary/binary operations and the array repeats to function calls. //! For instance, we desugar ArrayToSlice from an unop to a function call. //! This allows a more uniform treatment later on. //! TODO: actually transform all the unops and binops to function calls? - -#![allow(dead_code)] - use crate::expressions::{Rvalue, UnOp}; -use crate::llbc_ast::{iter_function_bodies, iter_global_bodies}; -use crate::llbc_ast::{ - AssumedFunId, Call, CtxNames, FunDecls, FunId, GlobalDecls, RawStatement, Statement, -}; -use crate::types::ErasedRegion; -use crate::types::RefKind; +use crate::formatter::Formatter; +use crate::llbc_ast::*; +use crate::translate_ctx::TransCtx; +use crate::types::*; -fn transform_st(s: &mut Statement) -> Vec { +fn transform_st(s: &mut Statement) -> Option> { match &s.content { // Transform the ArrayToSlice unop RawStatement::Assign(p, Rvalue::UnaryOp(UnOp::ArrayToSlice(ref_kind, ty, cg), op)) => { @@ -23,35 +18,67 @@ fn transform_st(s: &mut Statement) -> Vec { RefKind::Mut => AssumedFunId::ArrayToSliceMut, RefKind::Shared => AssumedFunId::ArrayToSliceShared, }; - let func = FunId::Assumed(id); - let region_args = vec![ErasedRegion::Erased]; - let type_args = vec![ty.clone()]; - let const_generic_args = vec![cg.clone()]; + let func = FunIdOrTraitMethodRef::mk_assumed(id); + let generics = GenericArgs::new( + vec![Region::Erased], + vec![ty.clone()], + vec![cg.clone()], + vec![], + ); + let func = FnPtr { + func, + generics, + trait_and_method_generic_args: None, + }; + s.content = RawStatement::Call(Call { + func, + args: vec![op.clone()], + dest: p.clone(), + }); + + None + } + // Transform the array aggregates to function calls + RawStatement::Assign(p, Rvalue::Repeat(op, ty, cg)) => { + // We could avoid the clone operations below if we take the content of + // the statement. In practice, this shouldn't have much impact. + let id = AssumedFunId::ArrayRepeat; + let func = FunIdOrTraitMethodRef::mk_assumed(id); + let generics = GenericArgs::new( + vec![Region::Erased], + vec![ty.clone()], + vec![cg.clone()], + vec![], + ); + let func = FnPtr { + func, + generics, + trait_and_method_generic_args: None, + }; s.content = RawStatement::Call(Call { func, - region_args, - type_args, - const_generic_args, args: vec![op.clone()], dest: p.clone(), }); - vec![] + None } - _ => vec![], + _ => None, } } -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to transform some operations to function calls: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to transform some operations to function calls: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); b.body.transform(&mut transform_st); trace!( - "# After transforming some operations to function calls: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# After transforming some operations to function calls: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); } } diff --git a/charon/src/reconstruct_asserts.rs b/charon/src/reconstruct_asserts.rs index b5e13472..cc64a7a2 100644 --- a/charon/src/reconstruct_asserts.rs +++ b/charon/src/reconstruct_asserts.rs @@ -5,76 +5,41 @@ use take_mut::take; -use crate::{ - llbc_ast::{Assert, CtxNames, FunDecls, GlobalDecls, RawStatement, Statement, Switch}, - ullbc_ast::{iter_function_bodies, iter_global_bodies}, -}; -use std::iter::FromIterator; +use crate::formatter::Formatter; +use crate::gast::{iter_function_bodies, iter_global_bodies}; +use crate::llbc_ast::*; +use crate::translate_ctx::TransCtx; -fn transform_st(mut st: Statement) -> Statement { - st.content = match st.content { - RawStatement::Assign(p, rv) => RawStatement::Assign(p, rv), - RawStatement::FakeRead(p) => RawStatement::FakeRead(p), - RawStatement::SetDiscriminant(p, vid) => RawStatement::SetDiscriminant(p, vid), - RawStatement::Drop(p) => RawStatement::Drop(p), - RawStatement::Assert(assert) => RawStatement::Assert(assert), - RawStatement::Call(call) => RawStatement::Call(call), - RawStatement::Panic => RawStatement::Panic, - RawStatement::Return => RawStatement::Return, - RawStatement::Break(i) => RawStatement::Break(i), - RawStatement::Continue(i) => RawStatement::Continue(i), - RawStatement::Nop => RawStatement::Nop, - RawStatement::Switch(switch) => { - match switch { - Switch::If(op, st1, st2) => { - let st2 = Box::new(transform_st(*st2)); - - // Check if the first statement is a panic: if yes, replace - // the if .. then ... else ... by an assertion. - if st1.content.is_panic() { - let st1 = Statement::new( - st1.meta, - RawStatement::Assert(Assert { - cond: op, - expected: false, - }), - ); - let st1 = Box::new(st1); - - RawStatement::Sequence(st1, st2) - } else { - let switch = Switch::If(op, Box::new(transform_st(*st1)), st2); - RawStatement::Switch(switch) - } - } - Switch::SwitchInt(op, int_ty, targets, mut otherwise) => { - let targets = - Vec::from_iter(targets.into_iter().map(|(v, e)| (v, transform_st(e)))); - *otherwise = transform_st(*otherwise); - let switch = Switch::SwitchInt(op, int_ty, targets, otherwise); - RawStatement::Switch(switch) - } - Switch::Match(_, _, _) => { - // This variant is introduced in a subsequent pass - unreachable!(); - } - } - } - RawStatement::Loop(loop_body) => RawStatement::Loop(Box::new(transform_st(*loop_body))), - RawStatement::Sequence(st1, st2) => { - RawStatement::Sequence(Box::new(transform_st(*st1)), Box::new(transform_st(*st2))) +fn transform_st(st: &mut Statement) -> Option> { + if let RawStatement::Switch(Switch::If(_, st1, _)) = &mut st.content { + // Check if the first statement is a panic: if yes, replace + // the if .. then ... else ... by an assertion. + if st1.content.is_panic() { + // Replace: we need to take the value + take(&mut st.content, |st| { + let (op, st1, st2) = st.to_switch().to_if(); + let st1 = Statement::new( + st1.meta, + RawStatement::Assert(Assert { + cond: op, + expected: false, + }), + ); + let st1 = Box::new(st1); + RawStatement::Sequence(st1, st2) + }); } - }; - - st + } + None } -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to reconstruct asserts in decl: {name}\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to reconstruct asserts in decl: {}\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); - take(&mut b.body, transform_st); + b.body.transform(&mut transform_st); } } diff --git a/charon/src/regions_hierarchy.rs b/charon/src/regions_hierarchy.rs deleted file mode 100644 index 3c57ec68..00000000 --- a/charon/src/regions_hierarchy.rs +++ /dev/null @@ -1,805 +0,0 @@ -//! Analysis of type defintions and function signatures to compute the -//! hierarchy between regions. -#![allow(dead_code)] - -use crate::common::*; -use crate::formatter::Formatter; -use crate::graphs::*; -use crate::llbc_ast::FunDecls; -use crate::reorder_decls as rd; -use crate::reorder_decls::{DeclarationGroup, DeclarationsGroups}; -use crate::translate_ctx::TransCtx; -use crate::types as ty; -use crate::types::*; -use crate::ullbc_ast::{FunDeclId, FunSig}; -use hashlink::linked_hash_map::LinkedHashMap; -use macros::generate_index_type; -use petgraph::algo::tarjan_scc; -use petgraph::graphmap::DiGraphMap; -use petgraph::Direction; -use serde::Serialize; -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; - -generate_index_type!(RegionGroupId); - -pub type TypeDeclarationGroup = rd::GDeclarationGroup; - -pub fn region_group_id_to_pretty_string(rid: RegionGroupId::Id) -> String { - format!("rg@{rid}") -} - -#[derive(Copy, Clone)] -pub struct LifetimeConstraint { - region: Region, - parent: Region, -} - -/// An edge from r1 to r2 means: -/// r1 : r2 (i.e.: r1 lasts longer than r2) -type LifetimeConstraints = DiGraphMap, ()>; - -/// A group of regions. -/// -/// Is used to group regions with the same lifetime together, and express -/// the lifetime hierarchy between different groups of regions. -#[derive(Debug, Clone, Serialize)] -pub struct RegionGroup { - /// The region group identifier - pub id: RegionGroupId::Id, - /// The regions included in this group - pub regions: Vec, - /// The parent region groups - pub parents: Vec, -} - -pub type RegionGroups = RegionGroupId::Vector; - -/// Compute the region strongly connected components from a constraints graph. -fn compute_sccs_from_lifetime_constraints( - constraints_graph: &LifetimeConstraints, - region_params: &RegionVarId::Vector, -) -> SCCs> { - // Apply Tarjan's algorithms to group the regions and the borrows which have - // the same lifetime. We then reorder those group of regions to be as close - // as possible to the original order. - let region_sccs = tarjan_scc(&constraints_graph); - - // Reorder the SCCs - let get_region_parents = &|r: Region| { - constraints_graph - .neighbors_directed(r, Direction::Outgoing) - .collect() - }; - - // Option::iter is a trick to easily append a single region to the var regions - // Maybe there is a better way. - let all_var_regions = region_params.iter_indices().map(Region::Var); - let all_rids: Vec> = all_var_regions - .chain(Some(Region::Static).into_iter()) - .collect(); - let sccs = reorder_sccs(get_region_parents, &all_rids, ®ion_sccs); - - // Debugging - trace!( - "{}", - vec_to_string( - &|scc: &Vec>| { - let ids: Vec = scc.iter().map(|r| r.to_string()).collect(); - format!("[{}]", ids.join(", ")) - }, - &sccs.sccs - ) - ); - - sccs -} - -/// The computation of a regions hierarchy is done in two steps: -/// - first we visit the type definition/function signature to register the -/// constraints between the different regions -/// - then we compute the hierarchy from those accumulated constraints -/// This function performs the second step. -fn compute_regions_hierarchy_from_constraints( - mut constraints: SCCs>, -) -> RegionGroups { - // The last SCC **MUST** contain the static region. - // For now, we don't handle cases where regions different from 'static - // can live as long as 'static, so we check that the last scc is the - // {static} singleton. - // TODO: general support for 'static - assert!(!constraints.sccs.is_empty()); - assert!(constraints.sccs.last().unwrap() == &vec![Region::Static]); - - // Pop the last SCC (which is {'static}). - let _ = constraints.sccs.pop(); - let _ = constraints.scc_deps.pop(); - - // Compute the hierarchy - let mut groups = RegionGroups::new(); - for (i, scc) in constraints.sccs.into_iter().enumerate() { - // Compute the id - let id = RegionGroupId::Id::new(i); - - // Retrieve the set of regions in the group - let regions: Vec = scc.into_iter().map(|r| *r.as_var()).collect(); - - // Compute the set of parent region groups - let parents: Vec = constraints - .scc_deps - .get(i) - .unwrap() - .iter() - .map(|j| RegionGroupId::Id::new(*j)) - .collect(); - - // Push the group - groups.push_back(RegionGroup { - id, - regions, - parents, - }); - } - - // Return - groups -} - -/// See [TypesConstraintsMap] -pub type RegionVarsConstraintsMap = - LinkedHashMap>>; - -/// See [TypesConstraintsMap] -pub type TypeVarsConstraintsMap = LinkedHashMap>>; - -/// See [TypesConstraintsMap] -#[derive(Debug, Clone)] -pub struct TypeDeclConstraintsMap { - region_vars_constraints: RegionVarsConstraintsMap, - type_vars_constraints: TypeVarsConstraintsMap, -} - -impl TypeDeclConstraintsMap { - fn new() -> Self { - TypeDeclConstraintsMap { - region_vars_constraints: RegionVarsConstraintsMap::new(), - type_vars_constraints: TypeVarsConstraintsMap::new(), - } - } -} - -/// This map gives, for every definition: -/// - for every region parameter: the set of regions which outlive this region -/// parameter. -/// - for every type parameter: the set of regions under which the type parameter -/// appears. This means that every region appearing in this type parameter's -/// instantiation must outlive the regions in this set. -/// -/// For instance, for the following type definition: -/// ```text -/// struct S<'a, 'b, T1, T2> { -/// x : T1, -/// y : &'a mut &'b mut T2, -/// } -/// ``` -/// -/// We would have: -/// ```text -/// 'a -> {} -/// 'b -> {'a} -/// -/// T1 -> {} -/// T2 -> {'a, 'b} -/// ``` -/// -/// Note: we use linked hash maps to preserve the order for printing. -pub type TypesConstraintsMap = LinkedHashMap; - -fn add_region_constraints( - updated: &mut bool, - acc_constraints: &mut LifetimeConstraints, - type_def_constraints: &mut Option, - region: Region, - parent_regions: &im::HashSet>, -) { - // Check that the region is indeed in the nodes - if !acc_constraints.contains_node(region) { - *updated = true; - acc_constraints.add_node(region); - } - - for parent in parent_regions.iter() { - let parent = *parent; - if !acc_constraints.contains_node(parent) { - *updated = true; - acc_constraints.add_node(parent); - } - if !acc_constraints.contains_edge(region, parent) { - *updated = true; - acc_constraints.add_edge(region, parent, ()); - } - } - - match (®ion, type_def_constraints) { - (_, None) | (Region::Static, _) => (), - (Region::Var(rid), Some(type_def_constraints)) => { - let current_parents = type_def_constraints - .region_vars_constraints - .get_mut(rid) - .unwrap(); - for parent in parent_regions.iter() { - if !current_parents.contains(parent) { - *updated = true; - current_parents.insert(*parent); - } - } - } - } - - // Also constrain with regards to static: - if !acc_constraints.contains_edge(Region::Static, region) { - *updated = true; - acc_constraints.add_edge(Region::Static, region, ()); - } -} - -/// TODO: detailed explanations -fn compute_full_regions_constraints_for_ty( - updated: &mut bool, - constraints_map: &TypesConstraintsMap, - acc_constraints: &mut LifetimeConstraints, - type_def_constraints: &mut Option, // TODO: rename - parent_regions: im::HashSet>, - ty: &RTy, -) { - match ty { - Ty::Adt(type_id, regions, types, _) => { - // Introduce constraints for all the regions given as parameters - for r in regions { - add_region_constraints( - updated, - acc_constraints, - type_def_constraints, - *r, - &parent_regions, - ); - } - - // Compute the map from region param id to region instantation, for - // this ADT instantiation - let region_inst: RegionVarId::Vector> = - RegionVarId::Vector::from_iter(regions.iter().copied()); - - // Lookup the constraints for this type definition - match type_id { - TypeId::Adt(type_def_id) => { - // Lookup the (non-instantiated) type parameter constraints for this ADT - let adt_constraints = constraints_map.get(type_def_id).unwrap(); - - // Add the region constraints - for (region_param_id, region) in region_inst.iter_indexed_values() { - let additional_parents = adt_constraints - .region_vars_constraints - .get(®ion_param_id) - .unwrap(); - - // We need to instantiate the additional parents - let additional_parents = - im::HashSet::from_iter(additional_parents.iter().map(|r| match r { - Region::Static => Region::Static, - Region::Var(rid) => *region_inst.get(*rid).unwrap(), - })); - - // Add the constraints - add_region_constraints( - updated, - acc_constraints, - type_def_constraints, - *region, - &additional_parents, - ); - } - - // Explore the types given as parameters - let types: TypeVarId::Vector<&RTy> = TypeVarId::Vector::from_iter(types.iter()); - for (type_param_id, fty) in types.iter_indexed_values() { - // Retrieve the (non-instantiated) parent regions for this type param - let type_param_constraints = adt_constraints - .type_vars_constraints - .get(&type_param_id) - .unwrap(); - - // Instantiate the parent regions constraints - let mut parent_regions = parent_regions.clone(); - for r in type_param_constraints { - let region = match r { - Region::Static => Region::Static, - Region::Var(rid) => *region_inst.get(*rid).unwrap(), - }; - parent_regions.insert(region); - } - - // Explore the type parameter - compute_full_regions_constraints_for_ty( - updated, - constraints_map, - acc_constraints, - type_def_constraints, - parent_regions.clone(), - fty, - ); - } - } - TypeId::Tuple - | TypeId::Assumed( - AssumedTy::Box - | AssumedTy::Vec - | AssumedTy::Option - | AssumedTy::PtrUnique - | AssumedTy::Str - | AssumedTy::PtrNonNull - | AssumedTy::Array - | AssumedTy::Slice - | AssumedTy::Range, - ) => { - // Explore the types given as parameters - for fty in types { - compute_full_regions_constraints_for_ty( - updated, - constraints_map, - acc_constraints, - type_def_constraints, - parent_regions.clone(), - fty, - ); - } - } - } - } - Ty::Literal(_) | Ty::Never => { - // Nothing to do - } - Ty::Ref(region, ref_ty, _mutability) => { - // Add the constraint for the region in the reference - add_region_constraints( - updated, - acc_constraints, - type_def_constraints, - *region, - &parent_regions, - ); - - // Update the parent regions, then continue exploring - let mut parent_regions = parent_regions.clone(); - parent_regions.insert(*region); - compute_full_regions_constraints_for_ty( - updated, - constraints_map, - acc_constraints, - type_def_constraints, - parent_regions, - ref_ty, - ); - } - Ty::RawPtr(ptr_ty, _) => { - // Dive in - compute_full_regions_constraints_for_ty( - updated, - constraints_map, - acc_constraints, - type_def_constraints, - parent_regions, - ptr_ty, - ); - } - Ty::TypeVar(var_id) => { - // Add the parent regions in the set of parent regions for the type variable - match type_def_constraints { - None => (), - Some(type_def_constraints) => { - let parents_set = type_def_constraints - .type_vars_constraints - .get_mut(var_id) - .unwrap(); - for parent in parent_regions { - if !parents_set.contains(&parent) { - *updated = true; - parents_set.insert(parent); - } - } - } - } - } - } -} - -/// Auxiliary function. -/// -/// Compute the region constraints for a type declaration group. -/// -/// Note that recursive types in rust are very general. For instance, they allow -/// non-uniform polymorphism: -/// ```text -/// enum E { -/// V1, -/// V2(Box>) -/// } -/// ``` -/// -/// Following this, we compute the constraints by computing a fixed -/// point: for every variant of every type appearing in the type declaration -/// group, we compute a properly initialized set of constraints. -/// We then explore all those types: as long as exploring one of those types -/// leads to a new constraint, we reexplore them all. -fn compute_regions_constraints_for_type_decl_group( - constraints_map: &mut TypesConstraintsMap, - types: &TypeDecls, - decl: &TypeDeclarationGroup, -) -> Vec>> { - // List the type ids in the type declaration group - let type_ids: HashSet = match decl { - TypeDeclarationGroup::NonRec(id) => { - let mut ids = HashSet::new(); - ids.insert(*id); - ids - } - TypeDeclarationGroup::Rec(ids) => HashSet::from_iter(ids.iter().copied()), - }; - - // Initialize the constraints map - TODO: this will be different once we - // support constraints over the generics in the definitions - for id in type_ids.iter() { - let type_def = types.get(*id).unwrap(); - let region_vars_constraints = RegionVarsConstraintsMap::from_iter( - type_def - .region_params - .iter() - .map(|var| (var.index, HashSet::new())), - ); - let type_vars_constraints = TypeVarsConstraintsMap::from_iter( - type_def - .type_params - .iter() - .map(|var| (var.index, HashSet::new())), - ); - let type_def_constraints = TypeDeclConstraintsMap { - region_vars_constraints, - type_vars_constraints, - }; - constraints_map.insert(*id, type_def_constraints); - } - - let mut updated = true; - let mut acc_constraints_map: HashMap = - HashMap::from_iter(type_ids.iter().map(|id| { - (*id, { - let mut graph = LifetimeConstraints::new(); - graph.add_node(Region::Static); - graph - }) - })); - - while updated { - updated = false; - - // Accumulate constraints for every variant of every type - for id in type_ids.iter() { - let type_def = types.get(*id).unwrap(); - - // If the type is transparent, we explore the ADT variants. - // If the type is opaque, there is nothing to do. - // TODO: will be slightly different once we support constraints - // over the generics in the type declarations - - // Instantiate the type definition variants - let region_params = Vec::from_iter( - type_def - .region_params - .iter() - .map(|rvar| Region::Var(rvar.index)), - ); - let type_params = Vec::from_iter( - type_def - .type_params - .iter() - .map(|tvar| Ty::TypeVar(tvar.index)), - ); - let variants_fields_tys = - type_def.get_instantiated_variants(®ion_params, &type_params); - - match variants_fields_tys { - Option::None => { - // Opaque type: nothing to do - } - Option::Some(variants_fields_tys) => { - // Transparent type - - // Retreive the accumulated constraints for this type def - let acc_constraints = acc_constraints_map.get_mut(id).unwrap(); - - // Clone the type vars constraints map - we can't accumulate in the - // original map, so we have to clone - // TODO: this is not efficient - but the sets should be super small - let mut updt_type_vars_constraints = - Some(constraints_map.get(id).unwrap().clone()); - - // Explore the field types of all the variants - for field_tys in variants_fields_tys.iter() { - for ty in field_tys.iter() { - compute_full_regions_constraints_for_ty( - &mut updated, - constraints_map, - acc_constraints, - &mut updt_type_vars_constraints, - im::HashSet::new(), - ty, - ); - } - } - - // Update the type vars constraints map - let updt_type_vars_constraints = updt_type_vars_constraints.unwrap(); - let type_def_constraints = constraints_map.get_mut(id).unwrap(); - let region_vars_constraints = &mut type_def_constraints.region_vars_constraints; - let type_vars_constraints = &mut type_def_constraints.type_vars_constraints; - - // The constraints over region parameters - for (r_id, updt_set) in - updt_type_vars_constraints.region_vars_constraints.iter() - { - let set = region_vars_constraints.get_mut(r_id).unwrap(); - for r in updt_set.iter() { - set.insert(*r); - } - } - - // The constraints over type parameters - for (var_id, updt_set) in - updt_type_vars_constraints.type_vars_constraints.iter() - { - let set = type_vars_constraints.get_mut(var_id).unwrap(); - for r in updt_set.iter() { - set.insert(*r); - } - } - } - } - } - } - - // Compute the SCCs - let mut sccs_vec: Vec>> = Vec::new(); - for id in type_ids.iter() { - let type_def = types.get(*id).unwrap(); - let sccs = compute_sccs_from_lifetime_constraints( - acc_constraints_map.get(id).unwrap(), - &type_def.region_params, - ); - sccs_vec.push(sccs); - } - - // Return - sccs_vec -} - -/// Compute the region hierarchy (the order between the region's lifetimes) -/// for a (group of mutually recursive) type definitions. -/// Note that [TypeDecl] already contains a regions hierarchy: when translating -/// function signatures, we first translate the signature without this hierarchy, -/// then compute this hierarchy and add it to the type definition: this is -/// why this function performs in-place modifications instead of returning -/// a [RegionGroups]. -pub fn compute_regions_hierarchy_for_type_decl_group( - constraints_map: &mut TypesConstraintsMap, - types: &mut TypeDecls, - decl: &TypeDeclarationGroup, -) { - // Compute the constraints for every definition in the declaration group - let constraints = compute_regions_constraints_for_type_decl_group(constraints_map, types, decl); - - // Compute the regions hierarchies from every set of constraints, and - // update the type definitions - let type_ids: Vec = match decl { - TypeDeclarationGroup::NonRec(id) => vec![*id], - TypeDeclarationGroup::Rec(ids) => ids.clone(), - }; - for (id, sccs) in type_ids.into_iter().zip(constraints.into_iter()) { - let regions_group = compute_regions_hierarchy_from_constraints(sccs); - - let type_def = types.get_mut(id).unwrap(); - type_def.regions_hierarchy = regions_group; - } -} - -/// Compute the constraints between the different regions of a type (which -/// region lasts longer than which other region, etc.). -/// Note that the region hierarchies should already have been computed for all -/// the types: this function should be used when computing constraints for -/// **function signatures** only. -fn compute_regions_constraints_for_ty( - constraints_map: &TypesConstraintsMap, - acc_constraints: &mut LifetimeConstraints, - ty: &RTy, -) { - // We need to provide some values to [compute_full_regions_constraints_for_ty], - // but we don't use them in the present case (they are use by this function - // to communicate us information). - let mut updated = false; - let type_def_constraints = &mut None; - compute_full_regions_constraints_for_ty( - &mut updated, - constraints_map, - acc_constraints, - type_def_constraints, - im::HashSet::new(), - ty, - ) -} - -/// Compute the constraints between the different regions of a function -/// signature (which region lasts longer than which other region, etc.). -/// This is used to compute the order (in given by the region lifetime's) -/// between the regions. -/// TODO: rename. compute_ordered_regions_constraints...? -fn compute_regions_constraints_for_sig( - types_constraints: &TypesConstraintsMap, - sig: &FunSig, -) -> SCCs> { - let mut constraints_graph = LifetimeConstraints::new(); - constraints_graph.add_node(Region::Static); - - for input_ty in &sig.inputs { - compute_regions_constraints_for_ty(types_constraints, &mut constraints_graph, input_ty); - } - compute_regions_constraints_for_ty(types_constraints, &mut constraints_graph, &sig.output); - - // Compute the SCCs from the region constraints - compute_sccs_from_lifetime_constraints(&constraints_graph, &sig.region_params) -} - -/// Compute the region hierarchy (the order between the region's lifetimes) -/// for a function signature. -/// Note that [FunSig] already contains a regions hierarchy: when translating -/// function signatures, we first translate the signature without this hierarchy, -/// then compute this hierarchy and add it to the signature information. -pub fn compute_regions_hierarchy_for_sig( - types_constraints: &TypesConstraintsMap, - sig: &FunSig, -) -> RegionGroups { - // Compute the constraints between the regions and group them accordingly - let sccs = compute_regions_constraints_for_sig(types_constraints, sig); - - // Compute the regions hierarchy - compute_regions_hierarchy_from_constraints(sccs) -} - -/// Compute the region hierarchy (the order between the region's lifetimes) for -/// a set of function definitions. -pub fn compute_regions_hierarchies_for_functions( - types_constraints: &TypesConstraintsMap, - defs: &FunDecls, -) -> FunDeclId::Vector { - FunDeclId::Vector::from_iter( - defs.iter() - .map(|def| compute_regions_hierarchy_for_sig(types_constraints, &def.signature)), - ) -} - -impl RegionGroup { - pub fn fmt_with_ctx(&self, ctx: &T) -> String - where - T: Formatter, - { - // The parent region groups - let parents: Vec = self.parents.iter().map(|gid| gid.to_string()).collect(); - let parents = format!("{{{}}}", parents.join(",")); - - // The regions included in the group - let regions: Vec = self - .regions - .iter() - .map(|rid| ctx.format_object(*rid)) - .collect(); - let regions = format!("{{{}}}", regions.join(",")); - - // Put everything together - format!( - "{}{{parents={}}}={}", - region_group_id_to_pretty_string(self.id), - parents, - regions - ) - } -} - -fn types_def_constraints_map_fmt_with_ctx<'a, 'b, 'c, T>( - cs: &'a TypeDeclConstraintsMap, - ctx: &'b T, - indent: &'c str, -) -> String -where - T: Formatter - + Formatter - + Formatter<&'a Region>, -{ - let region_constraints = cs.region_vars_constraints.iter().map(|(rid, regions)| { - format!( - "{}{} -> {{{}}}", - indent, - ctx.format_object(*rid), - regions - .iter() - .map(|r| ctx.format_object(r)) - .collect::>() - .join(",") - ) - }); - let type_constraints = cs.type_vars_constraints.iter().map(|(vid, regions)| { - format!( - "{}{} -> {{{}}}", - indent, - ctx.format_object(*vid), - regions - .iter() - .map(|r| ctx.format_object(r)) - .collect::>() - .join(",") - ) - }); - let all_constraints: Vec = region_constraints.chain(type_constraints).collect(); - all_constraints.join(",\n") -} - -pub fn types_constraints_map_fmt_with_ctx( - cs: &TypesConstraintsMap, - types: &ty::TypeDecls, -) -> String { - // We iterate over the type definitions, not the types constraints map, - // in order to make sure we preserve the type definitions order - let types_constraints: Vec = types - .iter() - .map(|type_def| { - let cmap = cs.get(&type_def.def_id).unwrap(); - if type_def.region_params.len() + type_def.type_params.len() == 0 { - format!("{} -> []", types.format_object(type_def.def_id)) - } else { - let ctx = type_def; - format!( - "{} -> [\n{}\n]", - types.format_object(type_def.def_id), - types_def_constraints_map_fmt_with_ctx(cmap, ctx, " ") - ) - } - }) - .collect(); - types_constraints.join("\n") -} - -pub fn compute(ctx: &mut TransCtx, ordered_decls: &DeclarationsGroups) { - // First, compute the regions hierarchy for the types, and compute the types - // constraints map while doing so. We compute by working on a whole type - // declaration group at a time. - let mut types_constraints = TypesConstraintsMap::new(); - let type_defs = &mut ctx.type_defs; - for dgroup in ordered_decls { - match dgroup { - DeclarationGroup::Type(decl) => { - compute_regions_hierarchy_for_type_decl_group( - &mut types_constraints, - type_defs, - decl, - ); - } - DeclarationGroup::Fun(_) | DeclarationGroup::Global(_) => { - // Ignore the functions and constants - } - } - } - - // Use the types constraints map to compute the regions hierarchies for the - // function signatures - for decl in &mut ctx.fun_defs.iter_mut() { - decl.signature.regions_hierarchy = - compute_regions_hierarchy_for_sig(&mut types_constraints, &decl.signature); - } -} diff --git a/charon/src/regularize_constant_adts.rs b/charon/src/regularize_constant_adts.rs deleted file mode 100644 index 176694c6..00000000 --- a/charon/src/regularize_constant_adts.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! In MIR, compile-time constant ADTs are treated separately. -//! We don't want to have this distinction / redundancy in (U)LLBC. -//! -//! This pass removes all occurrences of [OperandConstantValue::Adt], -//! and builds regular ADTs ([Rvalue::Aggregate]) instead (as for static values). -//! -//! To do so, it recursively translates an operand of the form `const ` -//! to `AggregatedAdt`. The recursion happens on the assignment operands. - -use std::iter::zip; - -use crate::expressions::*; -use crate::meta::Meta; -use crate::types::*; -use crate::ullbc_ast::{ - iter_function_bodies, iter_global_bodies, make_locals_generator, CtxNames, FunDecls, - GlobalDecls, RawStatement, Statement, -}; -use crate::ullbc_ast_utils::body_transform_operands; -use crate::values::VarId; - -fn make_aggregate_kind(ty: &ETy, var_index: Option) -> AggregateKind { - let (id, regions, tys, cgs) = ty.as_adt(); - match id { - TypeId::Tuple => { - assert!(var_index.is_none()); - AggregateKind::Tuple - } - TypeId::Adt(decl_id) => { - let regions = regions.iter().cloned().collect(); - let tys = tys.iter().cloned().collect(); - let cgs = cgs.iter().cloned().collect(); - AggregateKind::Adt(*decl_id, var_index, regions, tys, cgs) - } - TypeId::Assumed(_) => unreachable!(), - } -} - -/// If the constant value is a constant ADT, push `Assign::Aggregate` statements -/// to the vector of statements, that bind new variables to the ADT parts and -/// the variable assigned to the complete ADT. -/// -/// Goes fom e.g. `f(T::A(x, y))` to `let a = T::A(x, y); f(a)`. -/// The function is recursively called on the aggregate fields (e.g. here x and y). -fn transform_constant_adt VarId::Id>( - meta: &Meta, - nst: &mut Vec, - ty: &ETy, - val: &OperandConstantValue, - make_new_var: &mut F, -) -> Option { - // Return early if there is nothing to decompose - let (variant, fields) = match val { - OperandConstantValue::Adt(v, f) => (v, f), - _ => return None, - }; - - // Translate fields recursively into statements and operands. - let ops = zip(ty.as_adt().2, fields) - .map(|(f_ty, f_val)| { - if let Some(var_id) = transform_constant_adt(meta, nst, f_ty, f_val, make_new_var) { - Operand::Move(Place::new(var_id)) - } else { - Operand::Const(f_ty.clone(), f_val.clone()) - } - }) - .collect(); - - // Produce the new variable holding the aggregate. - let rval = Rvalue::Aggregate(make_aggregate_kind(ty, *variant), ops); - let var_id = make_new_var(ty.clone()); - nst.push(Statement::new( - *meta, - RawStatement::Assign(Place::new(var_id), rval), - )); - Some(var_id) -} - -fn transform_operand_adt VarId::Id>( - meta: &Meta, - nst: &mut Vec, - op: &mut Operand, - f: &mut F, -) { - if let Operand::Const(ty, val) = op { - if let Some(var_id) = transform_constant_adt(meta, nst, ty, val, f) { - // Change the ADT constant operand to a move (of the extracted AST). - *op = Operand::Move(Place::new(var_id)); - } - } -} - -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { - for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { - trace!( - "# About to regularize constant ADTs in function: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) - ); - - let mut f = make_locals_generator(&mut b.locals); - body_transform_operands(&mut b.body, &mut |meta, nst, op| { - transform_operand_adt(meta, nst, op, &mut f) - }); - } -} diff --git a/charon/src/remove_drop_never.rs b/charon/src/remove_drop_never.rs index 903a1932..3229919c 100644 --- a/charon/src/remove_drop_never.rs +++ b/charon/src/remove_drop_never.rs @@ -3,7 +3,9 @@ //! `drop(v)` where `v` has type `Never` (it can happen - this module does the //! filtering). Then, we filter the unused variables ([crate::remove_unused_locals]). -use crate::llbc_ast::{CtxNames, FunDecls, GlobalDecls, RawStatement, Statement, Var}; +use crate::formatter::Formatter; +use crate::llbc_ast::{FunDecls, GlobalDecls, RawStatement, Statement, Var}; +use crate::translate_ctx::TransCtx; use crate::ullbc_ast::{iter_function_bodies, iter_global_bodies}; use crate::values::*; @@ -29,20 +31,19 @@ fn transform_st(locals: &VarId::Vector, st: &mut Statement) { } } -/// `fmt_ctx` is used for pretty-printing purposes. -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to remove drops of variables with type ! in decl: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to remove drops of variables with type ! in decl: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); let locals = &b.locals; - // Compute the set of local variables b.body.transform(&mut |st| { transform_st(locals, st); - vec![] + None }); } } diff --git a/charon/src/remove_dynamic_checks.rs b/charon/src/remove_dynamic_checks.rs index 8840429c..21482d39 100644 --- a/charon/src/remove_dynamic_checks.rs +++ b/charon/src/remove_dynamic_checks.rs @@ -3,13 +3,10 @@ //! must lead to a panic in Rust (which is why those checks are always present, even when //! compiling for release). In our case, we take this into account in the semantics of our //! array/slice manipulation and arithmetic functions, on the verification side. - -#![allow(dead_code)] - -use crate::expressions::MutExprVisitor; -use crate::llbc_ast::{iter_function_bodies, iter_global_bodies}; -use crate::llbc_ast::{CtxNames, FunDecls, GlobalDecls, MutAstVisitor, Statement}; -use crate::types::MutTypeVisitor; +use crate::formatter::Formatter; +use crate::llbc_ast::*; +use crate::translate_ctx::TransCtx; +use crate::types::*; use take_mut::take; struct RemoveDynChecks {} @@ -17,6 +14,22 @@ struct RemoveDynChecks {} impl MutTypeVisitor for RemoveDynChecks {} impl MutExprVisitor for RemoveDynChecks {} +/// Check that a statement is exactly: +/// ```text +/// assert(move p == expected) +/// ``` +fn is_assert_move(p: &Place, s: &Statement, expected: bool) -> bool { + if let RawStatement::Assert(Assert { + cond: Operand::Move(ap), + expected: aexpected, + }) = &s.content + { + return ap == p && *aexpected == expected; + } + // Default + false +} + impl MutAstVisitor for RemoveDynChecks { fn spawn(&mut self, visitor: &mut dyn FnMut(&mut Self)) { visitor(self) @@ -25,83 +38,161 @@ impl MutAstVisitor for RemoveDynChecks { fn merge(&mut self) {} /// We simply detect sequences of the following shapes, and remove them: - /// # 1. Division/remainder + /// # 1. Division/remainder/multiplication /// ```text /// b := copy x == const 0 /// assert(move b == false) + /// ``` + /// + /// # 2. Addition/substraction/multiplication. + /// In release mode, the rust compiler inserts assertions only inside the + /// body of global constants. + /// ```text + /// r := x + y; + /// assert(move r.1 == false); + /// z := move r.0; + /// ``` /// - /// # 2. Arrays/slices + /// # 3. Arrays/slices /// ```text /// l := len(a) /// b := copy x < copy l /// assert(move b == true) /// ``` + /// + /// # Shifts + /// ```text + /// x := ...; + /// b := move x < const 32; // or another constant + /// assert(move b == true); /// ``` fn visit_statement(&mut self, s: &mut Statement) { - if s.content.is_sequence() { - let (s0, s1) = s.content.as_sequence(); - if s1.content.is_sequence() { - let (s1, s2) = s1.content.as_sequence(); - // Division/remainder - if s0.content.is_assign() && s1.content.is_assert() { - let (dest_p, rv) = s0.content.as_assign(); - let a = s1.content.as_assert(); - - if rv.is_binary_op() { - let (binop, _, _) = rv.as_binary_op(); - // We don't check that the second operand is 0... - let binop_ok = binop.is_eq() && !a.expected; - - if binop_ok && a.cond.is_move() { - let move_p = a.cond.as_move(); - - if move_p == dest_p { - // Eliminate the first two statements - take(s, |s| { - let (_, s1) = s.content.to_sequence(); - let (_, s2) = s1.content.to_sequence(); - *s2 - }); - self.visit_statement(s); - // Return so as not to take the default branch - return; - } - } - } - } + if let RawStatement::Sequence(s0, s1) = &s.content { + if let RawStatement::Sequence(s1, s2) = &s1.content { // Arrays/Slices - else if s0.content.is_assign() - && s1.content.is_assign() - && s2.content.is_sequence() - { - let (s2, _) = s2.content.as_sequence(); + if let ( // s0 should be: `l := len(a)` - let (dest_l_p, l_rv) = s0.content.as_assign(); + RawStatement::Assign(dest_l_p, Rvalue::Len(..)), // s1 should be: `b := copy x < copy l` - let (dest_b_p, b_rv) = s1.content.as_assign(); - if s2.content.is_assert() { - // s2 should be: `assert(move b == true)` - let a = s2.content.as_assert(); - - if l_rv.is_len() && b_rv.is_binary_op() { - let (binop, _, l_op) = b_rv.as_binary_op(); - let binop_ok = binop.is_lt() && a.expected && l_op.is_copy(); - - if binop_ok && a.cond.is_move() { - let l_op_place = l_op.as_copy(); - let move_p = a.cond.as_move(); + RawStatement::Assign( + dest_b_p, + Rvalue::BinaryOp(BinOp::Lt, _, Operand::Copy(l_op_place)), + ), + // s2 + RawStatement::Sequence(s2, _), + ) = (&s0.content, &s1.content, &s2.content) + { + // s2 should be: `assert(move b == true)` + if dest_l_p == l_op_place && is_assert_move(dest_b_p, s2, true) { + // Eliminate the first three statements + take(s, |s| { + let (_, s1) = s.content.to_sequence(); + let (_, s2) = s1.content.to_sequence(); + let (_, s3) = s2.content.to_sequence(); + *s3 + }); + self.visit_statement(s); + // Return so as not to take the default branch + return; + } + } + // Shift left + else if let ( + // s0 should be an assignment + RawStatement::Assign(dest_x_p, _), + // s1 should be: `b := copy x < const ...` + RawStatement::Assign( + dest_b_p, + Rvalue::BinaryOp(BinOp::Lt, Operand::Move(x_place), Operand::Const(..)), + ), + RawStatement::Sequence(s2, _), + ) = (&s0.content, &s1.content, &s2.content) + { + // s2 should be: `assert(move b == true)` + if dest_x_p == x_place && is_assert_move(dest_b_p, s2, true) { + // Eliminate the first three statements + take(s, |s| { + let (_, s1) = s.content.to_sequence(); + let (_, s2) = s1.content.to_sequence(); + let (_, s3) = s2.content.to_sequence(); + *s3 + }); + self.visit_statement(s); + // Return so as not to take the default branch + return; + } + } + // Division/remainder/addition/etc. + else if let RawStatement::Assign(dest_p, Rvalue::BinaryOp(binop, _, _)) = + &s0.content + { + // We don't check that the second operand is 0 in + // case we are in the division/remainder case + if matches!(binop, BinOp::Eq) && is_assert_move(dest_p, s1, false) { + // This should be the division/remainder case + // Eliminate the first two statements + take(s, |s| { + let (_, s1) = s.content.to_sequence(); + let (_, s2) = s1.content.to_sequence(); + *s2 + }); + self.visit_statement(s); + // Return so as not to take the default branch + return; + } else if let ( + RawStatement::Assert(Assert { + cond: Operand::Move(move_p), + .. + }), + RawStatement::Sequence(s2, _), + ) = (&s1.content, &s2.content) + { + // TODO: the last statement is not necessarily a sequence + // This should be the addition/subtraction/etc. case + assert!( + matches!(binop, BinOp::Add | BinOp::Sub | BinOp::Mul), + "{:?}", + binop + ); - if dest_l_p == l_op_place && move_p == dest_b_p { - // Eliminate the first three statements - take(s, |s| { - let (_, s1) = s.content.to_sequence(); - let (_, s2) = s1.content.to_sequence(); - let (_, s3) = s2.content.to_sequence(); - *s3 - }); - self.visit_statement(s); - // Return so as not to take the default branch - return; + if let RawStatement::Assign(_, Rvalue::Use(Operand::Move(move_p1))) = + &s2.content + { + // move_p should be: r.1 + // move_p1 should be: r.0 + if move_p.var_id == move_p1.var_id + && move_p.projection.len() == 1 + && move_p1.projection.len() == 1 + { + if let ( + ProjectionElem::Field(FieldProjKind::Tuple(..), fid0), + ProjectionElem::Field(FieldProjKind::Tuple(..), fid1), + ) = (&move_p.projection[0], &move_p1.projection[0]) + { + use crate::id_vector::ToUsize; + if fid0.to_usize() == 1 && fid1.to_usize() == 0 { + // Collapse into one assignment + take(s, |s| { + let (s0, s1) = s.content.to_sequence(); + let (_, s2) = s1.content.to_sequence(); + let (s2, s3) = s2.content.to_sequence(); + let (_, op) = s0.content.to_assign(); + let (dest, _) = s2.content.to_assign(); + let meta0 = s0.meta; + let s0 = RawStatement::Assign(dest, op); + let s0 = Statement { + meta: meta0, + content: s0, + }; + Statement { + meta: s2.meta, + content: RawStatement::Sequence(Box::new(s0), s3), + } + }); + self.visit_statement(s); + // Return so as not to take the default branch + return; + } } } } @@ -120,17 +211,19 @@ impl MutAstVisitor for RemoveDynChecks { } } -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to remove the dynamic checks: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to remove the dynamic checks: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); let mut visitor = RemoveDynChecks {}; visitor.visit_statement(&mut b.body); trace!( - "# After we removed the dynamic checks: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# After we removed the dynamic checks: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); } } diff --git a/charon/src/remove_nops.rs b/charon/src/remove_nops.rs new file mode 100644 index 00000000..bc22d8b9 --- /dev/null +++ b/charon/src/remove_nops.rs @@ -0,0 +1,38 @@ +//! Remove the useless no-ops. + +use crate::formatter::Formatter; +use crate::llbc_ast::{FunDecls, GlobalDecls, RawStatement, Statement}; +use crate::meta::combine_meta; +use crate::translate_ctx::TransCtx; +use crate::ullbc_ast::{iter_function_bodies, iter_global_bodies}; +use take_mut::take; + +fn transform_st(s: &mut Statement) { + if let RawStatement::Sequence(s1, _) = &s.content { + if s1.content.is_nop() { + take(s, |s| { + let (s1, s2) = s.content.to_sequence(); + Statement { + content: s2.content, + meta: combine_meta(&s1.meta, &s2.meta), + } + }) + } + } +} + +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { + for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { + trace!( + "# About to remove useless no-ops in decl: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) + ); + + // Compute the set of local variables + b.body.transform(&mut |st| { + transform_st(st); + None + }); + } +} diff --git a/charon/src/remove_read_discriminant.rs b/charon/src/remove_read_discriminant.rs index 7f9a6a91..2e629862 100644 --- a/charon/src/remove_read_discriminant.rs +++ b/charon/src/remove_read_discriminant.rs @@ -3,133 +3,120 @@ //! `drop(v)` where `v` has type `Never` (it can happen - this module does the //! filtering). Then, we filter the unused variables ([crate::remove_unused_locals]). -use take_mut::take; - -use crate::expressions::*; -use crate::llbc_ast::{ - new_sequence, CtxNames, FunDecls, GlobalDecls, RawStatement, Statement, Switch, -}; +use crate::formatter::Formatter; +use crate::llbc_ast::*; use crate::meta::combine_meta; +use crate::translate_ctx::TransCtx; use crate::types::*; use crate::ullbc_ast::{iter_function_bodies, iter_global_bodies}; use std::iter::FromIterator; -// TODO: don't consume `st`, use mutable borrows -fn transform_st(st: Statement) -> Statement { - let content = match st.content { - RawStatement::Assign(p, rv) => { - // Check that we never failed to remove a [Discriminant] - if let Rvalue::Discriminant(_) = &rv { - // Should have been filtered - unreachable!(); - } - RawStatement::Assign(p, rv) - } - RawStatement::FakeRead(p) => RawStatement::FakeRead(p), - RawStatement::SetDiscriminant(p, vid) => RawStatement::SetDiscriminant(p, vid), - RawStatement::Drop(p) => RawStatement::Drop(p), - RawStatement::Assert(assert) => RawStatement::Assert(assert), - RawStatement::Call(call) => RawStatement::Call(call), - RawStatement::Panic => RawStatement::Panic, - RawStatement::Return => RawStatement::Return, - RawStatement::Break(i) => RawStatement::Break(i), - RawStatement::Continue(i) => RawStatement::Continue(i), - RawStatement::Nop => RawStatement::Nop, - RawStatement::Switch(switch) => { - let switch = match switch { - Switch::If(op, st1, st2) => Switch::If( - op, - Box::new(transform_st(*st1)), - Box::new(transform_st(*st2)), - ), - Switch::SwitchInt(op, int_ty, targets, mut otherwise) => { - let targets = - Vec::from_iter(targets.into_iter().map(|(v, e)| (v, transform_st(e)))); - *otherwise = transform_st(*otherwise); - Switch::SwitchInt(op, int_ty, targets, otherwise) - } - Switch::Match(_, _, _) => { - // We shouldn't get there: this variant is introduced *during* - // this traversal - unreachable!(); - } - }; - RawStatement::Switch(switch) - } - RawStatement::Loop(loop_body) => RawStatement::Loop(Box::new(transform_st(*loop_body))), - RawStatement::Sequence(st1, st2) => { - if st1.content.is_assign() { - let (_, rv) = st1.content.as_assign(); - if rv.is_discriminant() { - let (dest, rv) = st1.content.to_assign(); - let p = rv.to_discriminant(); +struct Visitor<'a, 'tcx, 'ctx> { + _ctx: &'a TransCtx<'tcx, 'ctx>, +} - // The destination should be a variable - assert!(dest.projection.is_empty()); +impl<'a, 'tcx, 'ctx> Visitor<'a, 'tcx, 'ctx> { + fn update_raw_statement(&mut self, st: &mut RawStatement) { + match st { + RawStatement::Sequence( + box Statement { + content: RawStatement::Assign(dest, Rvalue::Discriminant(p)), + meta: meta1, + }, + box st2, + ) => { + // The destination should be a variable + assert!(dest.projection.is_empty()); - // A discriminant read must be immediately followed by a switch int. - // Note that it may be contained in a sequence, of course. - let (meta, switch, st3_opt) = match st2.content { - RawStatement::Sequence(st2, st3) => { - (st2.meta, st2.content.to_switch(), Some(*st3)) - } - RawStatement::Switch(switch) => (st2.meta, switch, None), - _ => unreachable!(), - }; - let (op, int_ty, targets, otherwise) = switch.to_switch_int(); - assert!(int_ty.is_isize()); - // The operand should be a [Move] applied to the variable `dest` - let op_p = op.to_move(); - assert!(op_p.projection.is_empty() && op_p.var_id == dest.var_id); + // Take st2 + let st2 = std::mem::replace( + st2, + Statement { + content: RawStatement::Nop, + meta: st2.meta, + }, + ); + + // A discriminant read must be immediately followed by a switch int. + // Note that it may be contained in a sequence, of course. + let (meta2, switch, st3_opt) = match st2.content { + RawStatement::Sequence( + box Statement { + content: RawStatement::Switch(switch), + meta: meta2, + }, + box st3, + ) => (meta2, switch, Some(st3)), + RawStatement::Switch(switch) => (st2.meta, switch, None), + _ => unreachable!(), + }; + + let Switch::SwitchInt(Operand::Move(op_p), int_ty, targets, otherwise) = switch + else { unreachable!() }; + assert!(int_ty.is_isize()); + assert!(op_p.projection.is_empty() && op_p.var_id == dest.var_id); + + let targets = Vec::from_iter(targets.into_iter().map(|(v, e)| { + ( + Vec::from_iter( + v.into_iter() + .map(|x| VariantId::Id::new(*x.as_isize() as usize)), + ), + e, + ) + })); - let targets = Vec::from_iter(targets.into_iter().map(|(v, e)| { - ( - Vec::from_iter( - v.into_iter() - .map(|x| VariantId::Id::new(*x.as_isize() as usize)), - ), - transform_st(e), - ) - })); - let otherwise = Box::new(transform_st(*otherwise)); - let switch = RawStatement::Switch(Switch::Match(p, targets, otherwise)); + let switch = RawStatement::Switch(Switch::Match(p.clone(), targets, otherwise)); - // Add the next statement if there is one - if let Some(st3) = st3_opt { - let meta = combine_meta(&st1.meta, &meta); - let switch = Statement { - meta, - content: switch, - }; - new_sequence(switch, st3).content - } else { - switch - } + // Add the next statement if there is one + *st = if let Some(st3) = st3_opt { + let meta = combine_meta(meta1, &meta2); + let switch = Statement { + meta, + content: switch, + }; + new_sequence(switch, st3).content } else { - let st1 = Box::new(transform_st(*st1)); - let st2 = Box::new(transform_st(*st2)); - RawStatement::Sequence(st1, st2) - } - } else { - let st1 = Box::new(transform_st(*st1)); - let st2 = Box::new(transform_st(*st2)); - RawStatement::Sequence(st1, st2) + switch + }; + } + RawStatement::Assign(_, Rvalue::Discriminant(_)) => { + // We failed to remove a [Discriminant] + unreachable!(); } + _ => (), } - }; + } +} - Statement::new(st.meta, content) +impl<'a, 'tcx, 'ctx> MutTypeVisitor for Visitor<'a, 'tcx, 'ctx> {} +impl<'a, 'tcx, 'ctx> MutExprVisitor for Visitor<'a, 'tcx, 'ctx> {} +impl<'a, 'tcx, 'ctx> MutAstVisitor for Visitor<'a, 'tcx, 'ctx> { + fn spawn(&mut self, visitor: &mut dyn FnMut(&mut Self)) { + visitor(self) + } + + fn merge(&mut self) {} + + fn visit_raw_statement(&mut self, st: &mut RawStatement) { + self.update_raw_statement(st); + + // Visit again, to make sure we transform the branches and + // the next statement, in case we updated, or to update the + // sub-statements, in case we didn't perform any updates. + self.default_visit_raw_statement(st); + } } -/// `fmt_ctx` is used for pretty-printing purposes. -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to remove [ReadDiscriminant] occurrences in decl: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to remove [ReadDiscriminant] occurrences in decl: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); - // Compute the set of local variables - take(&mut b.body, transform_st); + let mut visitor = Visitor { _ctx: ctx }; + visitor.visit_statement(&mut b.body); } } diff --git a/charon/src/remove_unused_locals.rs b/charon/src/remove_unused_locals.rs index ed8fe675..a8882f85 100644 --- a/charon/src/remove_unused_locals.rs +++ b/charon/src/remove_unused_locals.rs @@ -2,59 +2,17 @@ //! never used in the function bodies. This is useful to remove the locals with //! type `Never`. We actually check that there are no such local variables //! remaining afterwards. - -#![allow(dead_code)] - use crate::expressions::{MutExprVisitor, SharedExprVisitor}; +use crate::formatter::Formatter; use crate::id_vector::ToUsize; -use crate::llbc_ast::{ - CtxNames, FunDecls, GlobalDecls, MutAstVisitor, RawStatement, SharedAstVisitor, Statement, -}; -use crate::meta::combine_meta; +use crate::llbc_ast::{FunDecls, GlobalDecls, MutAstVisitor, SharedAstVisitor, Statement}; +use crate::translate_ctx::TransCtx; use crate::types::{MutTypeVisitor, SharedTypeVisitor}; use crate::ullbc_ast::{iter_function_bodies, iter_global_bodies, Var}; use crate::values::*; use std::collections::{HashMap, HashSet}; use take_mut::take; -struct RemoveNops {} - -impl MutTypeVisitor for RemoveNops {} -impl MutExprVisitor for RemoveNops {} - -impl MutAstVisitor for RemoveNops { - fn spawn(&mut self, visitor: &mut dyn FnMut(&mut Self)) { - visitor(self) - } - - fn merge(&mut self) {} - - fn visit_statement(&mut self, s: &mut Statement) { - match &s.content { - RawStatement::Sequence(s1, _) => { - if s1.content.is_nop() { - take(s, |s| { - let (s1, s2) = s.content.to_sequence(); - Statement { - content: s2.content, - meta: combine_meta(&s1.meta, &s2.meta), - } - }) - } else { - self.default_visit_raw_statement(&mut s.content) - } - } - _ => self.default_visit_raw_statement(&mut s.content), - } - } -} - -// TODO: remove? -pub(crate) fn remove_nops(s: &mut Statement) { - let mut v = RemoveNops {}; - v.visit_statement(s); -} - #[derive(Debug, Clone)] pub(crate) struct ComputeUsedLocals { vars: im::HashMap, @@ -166,11 +124,12 @@ fn update_locals( (locals, vids_map) } -pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut GlobalDecls) { +pub fn transform(ctx: &TransCtx, funs: &mut FunDecls, globals: &mut GlobalDecls) { for (name, b) in iter_function_bodies(funs).chain(iter_global_bodies(globals)) { trace!( - "# About to remove unused locals in decl: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# About to remove unused locals in decl: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); take(b, |mut b| { let (locals, vids_map) = update_locals(b.arg_count, b.locals, &b.body); @@ -180,8 +139,9 @@ pub fn transform(fmt_ctx: &CtxNames<'_>, funs: &mut FunDecls, globals: &mut Glob b }); trace!( - "# After removing unused locals of: {name}:\n{}", - b.fmt_with_ctx_names(fmt_ctx) + "# After removing unused locals of: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) ); // Check that there are no remaining locals with the type `Never` assert!(b.locals.iter().all(|v| !v.ty.is_never())); diff --git a/charon/src/reorder_decls.rs b/charon/src/reorder_decls.rs index 225737ea..91a3c038 100644 --- a/charon/src/reorder_decls.rs +++ b/charon/src/reorder_decls.rs @@ -1,27 +1,23 @@ use crate::common::*; -use crate::expressions::SharedExprVisitor; -use crate::gast::{FunDeclId, GlobalDeclId}; +use crate::expressions::*; +use crate::gast::*; use crate::graphs::*; use crate::translate_ctx::TransCtx; -use crate::types::{SharedTypeVisitor, TypeDeclId, TypeDeclKind}; -use crate::ullbc_ast::{ExprBody, SharedAstVisitor}; +use crate::types::*; +use crate::ullbc_ast::*; use hashlink::linked_hash_map::LinkedHashMap; use linked_hash_set::LinkedHashSet; -use macros::EnumAsGetters; -use macros::EnumIsA; -use macros::{VariantIndexArity, VariantName}; +use macros::{EnumAsGetters, EnumIsA, VariantIndexArity, VariantName}; use petgraph::algo::tarjan_scc; use petgraph::graphmap::DiGraphMap; -use rustc_hir::def_id::DefId; -use serde::ser::SerializeTupleVariant; -use serde::{Serialize, Serializer}; +use serde::Serialize; use std::fmt::{Debug, Display, Error, Formatter}; use std::vec::Vec; /// A (group of) top-level declaration(s), properly reordered. /// "G" stands for "generic" -#[derive(Debug, VariantIndexArity, VariantName)] -pub enum GDeclarationGroup { +#[derive(Debug, VariantIndexArity, VariantName, Serialize)] +pub enum GDeclarationGroup { /// A non-recursive declaration NonRec(Id), /// A (group of mutually) recursive declaration(s) @@ -29,19 +25,53 @@ pub enum GDeclarationGroup { } /// A (group of) top-level declaration(s), properly reordered. -#[derive(Debug, VariantIndexArity, VariantName)] -pub enum DeclarationGroup { +#[derive(Debug, VariantIndexArity, VariantName, Serialize)] +pub enum DeclarationGroup { /// A type declaration group - Type(GDeclarationGroup), + Type(GDeclarationGroup), /// A function declaration group - Fun(GDeclarationGroup), + Fun(GDeclarationGroup), /// A global declaration group - Global(GDeclarationGroup), + Global(GDeclarationGroup), + /// + TraitDecl(GDeclarationGroup), + /// + TraitImpl(GDeclarationGroup), } -impl DeclarationGroup { - fn make_type_group<'a>(is_rec: bool, gr: impl Iterator) -> Self { - let gr: Vec = gr.collect(); +impl GDeclarationGroup { + pub fn get_ids(&self) -> Vec { + use GDeclarationGroup::*; + match self { + NonRec(id) => vec![*id], + Rec(ids) => ids.clone(), + } + } +} + +impl GDeclarationGroup { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: crate::formatter::Formatter, + { + use GDeclarationGroup::*; + match self { + NonRec(id) => format!("Non rec: {}", ctx.format_object(*id)), + Rec(ids) => { + let ids = ids + .iter() + .map(|id| ctx.format_object(*id)) + .collect::>() + .join(", "); + format!("Rec: {}", ids) + } + } + } +} + +impl DeclarationGroup { + fn make_type_group(is_rec: bool, gr: impl Iterator) -> Self { + let gr: Vec<_> = gr.collect(); if is_rec { DeclarationGroup::Type(GDeclarationGroup::Rec(gr)) } else { @@ -50,8 +80,8 @@ impl DeclarationGroup(is_rec: bool, gr: impl Iterator) -> Self { - let gr: Vec = gr.collect(); + fn make_fun_group(is_rec: bool, gr: impl Iterator) -> Self { + let gr: Vec<_> = gr.collect(); if is_rec { DeclarationGroup::Fun(GDeclarationGroup::Rec(gr)) } else { @@ -60,8 +90,8 @@ impl DeclarationGroup(is_rec: bool, gr: impl Iterator) -> Self { - let gr: Vec = gr.collect(); + fn make_global_group(is_rec: bool, gr: impl Iterator) -> Self { + let gr: Vec<_> = gr.collect(); if is_rec { DeclarationGroup::Global(GDeclarationGroup::Rec(gr)) } else { @@ -69,6 +99,64 @@ impl DeclarationGroup, + ) -> Self { + let gr: Vec<_> = gr.collect(); + // Trait declarations often refer to `Self`, like below, + // which means they are often considered as recursive by our + // analysis. TODO: do something more precise. What is important + // is that we never use the "whole" self clause as argument, + // but rather projections over the self clause (like `::u`, + // in the declaration for `Foo`). + assert!( + gr.len() == 1, + "Invalid trait decl group:\n{}", + gr.iter() + .map(|id| ctx.format_object(*id)) + .collect::>() + .join("\n") + ); + DeclarationGroup::TraitDecl(GDeclarationGroup::NonRec(gr[0])) + } + + fn make_trait_impl_group( + ctx: &TransCtx, + is_rec: bool, + gr: impl Iterator, + ) -> Self { + let gr: Vec<_> = gr.collect(); + assert!( + !is_rec && gr.len() == 1, + "Invalid trait impl group:\n{}", + gr.iter() + .map(|id| ctx.format_object(*id)) + .collect::>() + .join("\n") + ); + DeclarationGroup::TraitImpl(GDeclarationGroup::NonRec(gr[0])) + } + + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: crate::formatter::Formatter + + crate::formatter::Formatter + + crate::formatter::Formatter + + crate::formatter::Formatter + + crate::formatter::Formatter, + { + use DeclarationGroup::*; + match self { + Type(g) => format!("Type decls group: {}", g.fmt_with_ctx(ctx)), + Fun(g) => format!("Fun decls group: {}", g.fmt_with_ctx(ctx)), + Global(g) => format!("Global decls group: {}", g.fmt_with_ctx(ctx)), + TraitDecl(g) => format!("Trait decls group: {}", g.fmt_with_ctx(ctx)), + TraitImpl(g) => format!("Trait impls group: {}", g.fmt_with_ctx(ctx)), + } + } } #[derive( @@ -85,10 +173,12 @@ impl DeclarationGroup { +pub enum AnyDeclId { Type(TypeId), Fun(FunId), Global(GlobalId), + TraitDecl(TraitDeclId), + TraitImpl(TraitImplId), } #[derive(Clone, Copy)] @@ -96,12 +186,11 @@ pub struct DeclInfo { pub is_transparent: bool, } -pub type DeclarationsGroups = - Vec>; +pub type DeclarationsGroups = Vec; /// We use the [Debug] trait instead of [Display] for the identifiers, because /// the rustc [DefId] doesn't implement [Display]... -impl Display for GDeclarationGroup { +impl Display for GDeclarationGroup { fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { match self { GDeclarationGroup::NonRec(id) => write!(f, "non-rec: {id:?}"), @@ -114,93 +203,71 @@ impl Display for GDeclarationGroup { } } -/// This is a bit annoying: because [DefId] and [Vec] doe't implement the -/// [Serialize] trait, we can't automatically derive the serializing trait... -impl Serialize for GDeclarationGroup { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - let enum_name = "GDeclarationGroup"; - let variant_name = self.variant_name(); - let (variant_index, variant_arity) = self.variant_index_arity(); - assert!(variant_arity > 0); - let mut vs = serializer.serialize_tuple_variant( - enum_name, - variant_index, - variant_name, - variant_arity, - )?; - match self { - GDeclarationGroup::NonRec(id) => { - vs.serialize_field(id)?; - } - GDeclarationGroup::Rec(ids) => { - let ids = VecSerializer::new(ids); - vs.serialize_field(&ids)?; - } - } - vs.end() - } -} - /// We use the [Debug] trait instead of [Display] for the identifiers, because /// the rustc [DefId] doesn't implement [Display]... -impl Display - for DeclarationGroup -{ +impl Display for DeclarationGroup { fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { match self { DeclarationGroup::Type(decl) => write!(f, "{{ Type(s): {decl} }}"), DeclarationGroup::Fun(decl) => write!(f, "{{ Fun(s): {decl} }}"), DeclarationGroup::Global(decl) => write!(f, "{{ Global(s): {decl} }}"), + DeclarationGroup::TraitDecl(decl) => write!(f, "{{ Trait decls(s): {decl} }}"), + DeclarationGroup::TraitImpl(decl) => write!(f, "{{ Trait impl(s): {decl} }}"), } } } -/// This is a bit annoying: because [DefId] and [Vec] doe't implement the -/// [Serialize] trait, we can't automatically derive the serializing trait... -impl Serialize - for DeclarationGroup -{ - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - let enum_name = "DeclarationGroup"; - let variant_name = self.variant_name(); - let (variant_index, variant_arity) = self.variant_index_arity(); - assert!(variant_arity > 0); - let mut vs = serializer.serialize_tuple_variant( - enum_name, - variant_index, - variant_name, - variant_arity, - )?; - match self { - DeclarationGroup::Type(decl) => { - vs.serialize_field(decl)?; - } - DeclarationGroup::Fun(decl) => { - vs.serialize_field(decl)?; - } - DeclarationGroup::Global(decl) => { - vs.serialize_field(decl)?; - } - } - vs.end() - } -} - -pub type AnyRustId = AnyDeclId; -pub type AnyTransId = AnyDeclId; +pub type AnyTransId = + AnyDeclId; pub struct Deps { dgraph: DiGraphMap, /// Want to make sure we remember the order of insertion graph: LinkedHashMap>, - /// We use this when exploring the graph + /// We use this when computing the graph current_id: Option, + /// We use this to track the trait impl block the current item belongs to + /// (if relevant). + /// + /// We use this to ignore the references to the parent impl block. + /// + /// If we don't do so, when computing our dependency graph we end up with + /// mutually recursive trait impl blocks/trait method impls in the presence + /// of associated types (the deepest reason is that we don't normalize the + /// types we query from rustc when translating the types from function + /// signatures - we avoid doing so because as of now it makes resolving + /// the trait params harder: if we get normalized types, we have to + /// implement a normalizer on our side to make sure we correctly match + /// types...). + /// + /// + /// For instance, the problem happens if in Rust we have: + /// ```text + /// pub trait WithConstTy { + /// type W; + /// fn f(x: &mut Self::W); + /// } + /// + /// impl WithConstTy for bool { + /// type W = u64; + /// fn f(_: &mut Self::W) {} + /// } + /// ``` + /// + /// In LLBC we get: + /// + /// ```text + /// impl traits::Bool::0 : traits::WithConstTy + /// { + /// type W = u64 with [] + /// fn f = traits::Bool::0::f + /// } + /// + /// fn traits::Bool::0::f<@R0>(@1: &@R0 mut (traits::Bool::0::W)) { .. } + /// // ^^^^^^^^^^^^^^^ + /// // refers to the trait impl + /// ``` + impl_trait_id: Option, } impl Deps { @@ -208,17 +275,48 @@ impl Deps { Deps { dgraph: DiGraphMap::new(), graph: LinkedHashMap::new(), - current_id: Option::None, + current_id: None, + impl_trait_id: None, } } - fn set_current_id(&mut self, id: AnyTransId) { + fn set_current_id(&mut self, ctx: &TransCtx, id: AnyTransId) { self.insert_node(id); self.current_id = Option::Some(id); + + // Add the id of the trait impl trait this item belongs to, if necessary + use AnyDeclId::*; + match id { + TraitDecl(_) | TraitImpl(_) => (), + Type(_) | Global(_) => { + // TODO + } + Fun(id) => { + // Lookup the function declaration. + // + // The declaration may not be present if we encountered errors. + if let Some(decl) = ctx.fun_defs.get(id) { + if let FunKind::TraitMethodImpl { + impl_id, + trait_id: _, + method_name: _, + provided: _, + } = &decl.kind + { + // Register the trait decl id + self.impl_trait_id = Some(*impl_id) + } + } else { + // Sanity check + assert!(ctx.error_count > 0); + } + } + } } fn unset_current_id(&mut self) { - self.current_id = Option::None; + self.current_id = None; + self.impl_trait_id = None; } fn insert_node(&mut self, id: AnyTransId) { @@ -250,6 +348,39 @@ impl SharedTypeVisitor for Deps { let id = AnyDeclId::Global(*id); self.insert_edge(id); } + + fn visit_trait_impl_id(&mut self, id: &TraitImplId::Id) { + // If the impl is the impl this item belongs to, we ignore it + // TODO: this is not very satisfying but this is the only way + // we have of preventing mutually recursive groups between + // method impls and trait impls in the presence of associated types... + if let Some(impl_id) = &self.impl_trait_id && impl_id == id { + // Ignore + } + else { + let id = AnyDeclId::TraitImpl(*id); + self.insert_edge(id); + } + } + + fn visit_trait_decl_id(&mut self, id: &TraitDeclId::Id) { + let id = AnyDeclId::TraitDecl(*id); + self.insert_edge(id); + } + + /// We override this method to not visit the trait decl. + /// + /// This is sound because the trait ref itself will either have a dependency + /// on the trait decl it implements, or it will refer to a clause which + /// will imply a dependency on the trait decl. + /// + /// The reason why we do this is that otherwise if a trait decl declares + /// a method which uses one of its associated types we will conclude that + /// the trait decl is recursive, while it isn't. + fn visit_trait_ref(&mut self, tr: &TraitRef) { + self.visit_trait_instance_id(&tr.trait_id); + self.visit_generic_args(&tr.generics); + } } impl SharedExprVisitor for Deps { @@ -275,58 +406,209 @@ impl Deps { } } } + + fn visit_generics_and_preds(&mut self, generics: &GenericParams, preds: &Predicates) { + // Visit the traits referenced in the generics + for clause in &generics.trait_clauses { + self.visit_trait_clause(clause); + } + + // Visit the predicates + self.visit_predicates(preds); + } + + /// Lookup a function and visit its signature + fn visit_fun_signature_from_trait(&mut self, ctx: &TransCtx, fid: FunDeclId::Id) { + let decl = ctx.fun_defs.get(fid).unwrap(); + self.visit_fun_sig(&decl.signature); + } +} + +impl AnyTransId { + fn fmt_with_ctx(&self, ctx: &TransCtx) -> String { + use AnyDeclId::*; + match self { + Type(id) => ctx.format_object(*id), + Fun(id) => ctx.format_object(*id), + Global(id) => ctx.format_object(*id), + TraitDecl(id) => ctx.format_object(*id), + TraitImpl(id) => ctx.format_object(*id), + } + } +} + +impl Deps { + fn fmt_with_ctx(&self, ctx: &TransCtx) -> String { + self.dgraph + .nodes() + .map(|node| { + let edges = self + .dgraph + .edges(node) + .map(|e| format!("\n {}", e.1.fmt_with_ctx(ctx))) + .collect::>() + .join(","); + + format!("{} -> [{}\n]", node.fmt_with_ctx(ctx), edges) + }) + .collect::>() + .join(",\n") + } } -pub fn reorder_declarations(ctx: &TransCtx) -> Result { +pub fn reorder_declarations(ctx: &TransCtx) -> DeclarationsGroups { trace!(); // Step 1: explore the declarations to build the graph let mut graph = Deps::new(); for id in &ctx.all_ids { - graph.set_current_id(*id); + graph.set_current_id(ctx, *id); match id { AnyTransId::Type(id) => { - let d = ctx.type_defs.get(*id).unwrap(); - use TypeDeclKind::*; - match &d.kind { - Struct(fields) => { - for f in fields { - graph.visit_ty(&f.ty) + if let Some(d) = ctx.type_defs.get(*id) { + use TypeDeclKind::*; + + // Visit the generics and the predicates + graph.visit_generics_and_preds(&d.generics, &d.preds); + + // Visit the body + match &d.kind { + Struct(fields) => { + for f in fields { + graph.visit_ty(&f.ty) + } } - } - Enum(vl) => { - for v in vl { - for f in &v.fields { - graph.visit_ty(&f.ty); + Enum(vl) => { + for v in vl { + for f in &v.fields { + graph.visit_ty(&f.ty); + } } } + Opaque | Error(_) => (), } - Opaque => (), + } else { + // There may have been errors + assert!(ctx.error_count > 0); } } AnyTransId::Fun(id) => { - let d = ctx.fun_defs.get(*id).unwrap(); + if let Some(d) = ctx.fun_defs.get(*id) { + // Explore the signature + let sig = &d.signature; + graph.visit_generics_and_preds(&sig.generics, &sig.preds); + for ty in &sig.inputs { + graph.visit_ty(ty); + } + graph.visit_ty(&sig.output); - // Explore the signature - for ty in &d.signature.inputs { - graph.visit_ty(ty); + // Explore the body + graph.visit_body(&d.body); + } else { + // There may have been errors + assert!(ctx.error_count > 0); } - graph.visit_ty(&d.signature.output); - - // Explore the body - graph.visit_body(&d.body); } AnyTransId::Global(id) => { - let d = ctx.global_defs.get(*id).unwrap(); + if let Some(d) = ctx.global_defs.get(*id) { + // Explore the body + graph.visit_body(&d.body); + } else { + // There may have been errors + assert!(ctx.error_count > 0); + } + } + AnyTransId::TraitDecl(id) => { + if let Some(d) = ctx.trait_decls.get(*id) { + // Visit the generics and the predicates + graph.visit_generics_and_preds(&d.generics, &d.preds); + + // Visit the parent clauses + for clause in &d.parent_clauses { + graph.visit_trait_clause(clause); + } + + // Visit the items + for (_, (ty, c)) in &d.consts { + graph.visit_ty(ty); + if let Some(id) = c { + graph.visit_global_decl_id(id); + } + } + + for (_, (clauses, ty)) in &d.types { + for c in clauses { + graph.visit_trait_clause(c); + } + if let Some(ty) = ty { + graph.visit_ty(ty); + } + } + + let method_ids = d.required_methods.iter().map(|(_, id)| *id).chain( + d.provided_methods + .iter() + .filter_map(|(_, id)| id.as_ref().copied()), + ); + for id in method_ids { + // Important: we must ignore the function id, because + // otherwise in the presence of associated types we may + // get a mutual recursion between the function and the + // trait. + // Ex: + // ``` + // trait Trait { + // type X; + // fn f(x : Trait::X); + // } + // ``` + graph.visit_fun_signature_from_trait(ctx, id) + } + } else { + // There may have been errors + assert!(ctx.error_count > 0); + } + } + AnyTransId::TraitImpl(id) => { + if let Some(d) = ctx.trait_impls.get(*id) { + // Visit the generics and the predicates + graph.visit_generics_and_preds(&d.generics, &d.preds); + + // Visit the implemented trait + graph.visit_trait_decl_id(&d.impl_trait.trait_id); + graph.visit_generic_args(&d.impl_trait.generics); + + // Visit the parent trait refs + for tr in &d.parent_trait_refs { + graph.visit_trait_ref(tr) + } + + // Visit the items + for (_, (ty, id)) in &d.consts { + graph.visit_ty(ty); + graph.visit_global_decl_id(id); + } + + for (_, (trait_refs, ty)) in &d.types { + graph.visit_ty(ty); + for trait_ref in trait_refs { + graph.visit_trait_ref(trait_ref); + } + } - // Explore the body - graph.visit_body(&d.body); + for (_, id) in d.required_methods.iter().chain(d.provided_methods.iter()) { + graph.visit_fun_decl_id(id) + } + } else { + // There may have been errors + assert!(ctx.error_count > 0); + } } } graph.unset_current_id(); } - trace!("Graph: {:?}", &graph.dgraph); + trace!("Graph:\n{}\n", graph.fmt_with_ctx(ctx)); // Step 2: Apply Tarjan's SCC (Strongly Connected Components) algorithm let sccs = tarjan_scc(&graph.dgraph); @@ -359,7 +641,14 @@ pub fn reorder_declarations(ctx: &TransCtx) -> Result { // The group should consist of only functions, only types or only one global. for id in scc { - assert!(id0.variant_index_arity() == id.variant_index_arity()); + assert!( + id0.variant_index_arity() == id.variant_index_arity(), + "Invalid scc:\n{}", + scc.iter() + .map(|x| x.fmt_with_ctx(ctx)) + .collect::>() + .join("\n") + ); } if let AnyDeclId::Global(_) = id0 { assert!(scc.len() == 1); @@ -375,7 +664,7 @@ pub fn reorder_declarations(ctx: &TransCtx) -> Result { // Note that we clone the vectors: it is not optimal, but they should // be pretty small. let is_rec = is_mutually_recursive || is_simply_recursive; - let group: DeclarationGroup = match id0 { + let group: DeclarationGroup = match id0 { AnyDeclId::Type(_) => DeclarationGroup::make_type_group( is_rec, scc.iter().map(AnyDeclId::as_type).copied(), @@ -387,6 +676,16 @@ pub fn reorder_declarations(ctx: &TransCtx) -> Result { is_rec, scc.iter().map(AnyDeclId::as_global).copied(), ), + AnyDeclId::TraitDecl(_) => DeclarationGroup::make_trait_decl_group( + ctx, + is_rec, + scc.iter().map(AnyDeclId::as_trait_decl).copied(), + ), + AnyDeclId::TraitImpl(_) => DeclarationGroup::make_trait_impl_group( + ctx, + is_rec, + scc.iter().map(AnyDeclId::as_trait_impl).copied(), + ), }; reordered_decls.push(group); @@ -394,7 +693,7 @@ pub fn reorder_declarations(ctx: &TransCtx) -> Result { trace!("{:?}", reordered_decls); - Ok(reordered_decls) + reordered_decls } #[cfg(test)] diff --git a/charon/src/simplify_constants.rs b/charon/src/simplify_constants.rs new file mode 100644 index 00000000..f240102a --- /dev/null +++ b/charon/src/simplify_constants.rs @@ -0,0 +1,142 @@ +//! The MIR constant expressions lead to a lot of duplication: there are +//! for instance constant ADTs which duplicate the "regular" aggregated +//! ADTs in the operands, constant references, etc. This reduces the number +//! of cases to handle and eases the function translation in Aeneas. +//! +//! This pass removes all those occurrences so that only the +//! [ConstantExpression::Literal]. It does so by introducing intermediate statements. +//! +//! A small remark about the intermediate statements we introduce for the globals: +//! we do so because, when evaluating the code in "concrete" mode, it allows to +//! handle the globals like function calls. + +use crate::expressions::*; +use crate::formatter::Formatter; +use crate::meta::Meta; +use crate::translate_ctx::TransCtx; +use crate::types::*; +use crate::ullbc_ast::{ + iter_function_bodies, iter_global_bodies, make_locals_generator, RawStatement, Statement, +}; +use crate::ullbc_ast_utils::body_transform_operands; +use crate::values::VarId; + +fn make_aggregate_kind(ty: &Ty, var_index: Option) -> AggregateKind { + let (id, generics) = ty.as_adt(); + AggregateKind::Adt(*id, var_index, generics.clone()) +} + +/// If the constant value is a constant ADT, push `Assign::Aggregate` statements +/// to the vector of statements, that bind new variables to the ADT parts and +/// the variable assigned to the complete ADT. +/// +/// Goes fom e.g. `f(T::A(x, y))` to `let a = T::A(x, y); f(a)`. +/// The function is recursively called on the aggregate fields (e.g. here x and y). +fn transform_constant_expr VarId::Id>( + meta: &Meta, + nst: &mut Vec, + val: ConstantExpr, + make_new_var: &mut F, +) -> Operand { + match val.value { + RawConstantExpr::Literal(_) + | RawConstantExpr::Var(_) + | RawConstantExpr::TraitConst(..) + | RawConstantExpr::FnPtr(..) => { + // Nothing to do + // TODO: for trait const: might come from a top-level impl, so we might + // want to introduce an intermediate statement to be able to evaluate + // it as a function call, like for globals. + Operand::Const(val) + } + RawConstantExpr::Global(global_id) => { + // Introduce an intermediate statement + let var_id = make_new_var(val.ty.clone()); + nst.push(Statement::new( + *meta, + RawStatement::Assign(Place::new(var_id), Rvalue::Global(global_id)), + )); + Operand::Move(Place::new(var_id)) + } + RawConstantExpr::Ref(box bval) => { + // Recurse on the borrowed value + let bval_ty = bval.ty.clone(); + let bval = transform_constant_expr(meta, nst, bval, make_new_var); + + // Introduce an intermediate statement to evaluate the referenced value + let bvar_id = make_new_var(bval_ty); + nst.push(Statement::new( + *meta, + RawStatement::Assign(Place::new(bvar_id), Rvalue::Use(bval)), + )); + + // Introduce an intermediate statement to borrow the value + let ref_var_id = make_new_var(val.ty); + let rvalue = Rvalue::Ref(Place::new(bvar_id), BorrowKind::Shared); + nst.push(Statement::new( + *meta, + RawStatement::Assign(Place::new(ref_var_id), rvalue), + )); + + // Return the new operand + Operand::Move(Place::new(ref_var_id)) + } + RawConstantExpr::Adt(variant, fields) => { + // Recurse on the fields + let fields = fields + .into_iter() + .map(|f| transform_constant_expr(meta, nst, f, make_new_var)) + .collect(); + + // Introduce an intermediate assignment for the aggregated ADT + let rval = Rvalue::Aggregate(make_aggregate_kind(&val.ty, variant), fields); + let var_id = make_new_var(val.ty); + nst.push(Statement::new( + *meta, + RawStatement::Assign(Place::new(var_id), rval), + )); + + // Return the new operand + Operand::Move(Place::new(var_id)) + } + } +} + +fn transform_operand VarId::Id>( + meta: &Meta, + nst: &mut Vec, + op: &mut Operand, + f: &mut F, +) { + // Transform the constant operands (otherwise do nothing) + take_mut::take(op, |op| { + if let Operand::Const(val) = op { + transform_constant_expr(meta, nst, val, f) + } else { + op + } + }) +} + +pub fn transform(ctx: &mut TransCtx) { + // Slightly annoying: we have to clone because of borrowing issues + let mut fun_defs = ctx.fun_defs.clone(); + let mut global_defs = ctx.global_defs.clone(); + + for (name, b) in iter_function_bodies(&mut fun_defs).chain(iter_global_bodies(&mut global_defs)) + { + trace!( + "# About to simplify constants in function: {}:\n{}", + name.fmt_with_ctx(ctx), + ctx.format_object(&*b) + ); + + let mut f = make_locals_generator(&mut b.locals); + body_transform_operands(&mut b.body, &mut |meta, nst, op| { + transform_operand(meta, nst, op, &mut f) + }); + } + + ctx.fun_defs = fun_defs; + ctx.global_defs = global_defs; +} diff --git a/charon/src/translate_constants.rs b/charon/src/translate_constants.rs index 626cf2ac..8bb71fa6 100644 --- a/charon/src/translate_constants.rs +++ b/charon/src/translate_constants.rs @@ -1,485 +1,191 @@ //! Functions to translate constants to LLBC. -#![allow(dead_code)] use crate::common::*; -use crate::expressions as e; -use crate::get_mir::extract_constants_at_top_level; +use crate::gast::*; use crate::translate_ctx::*; -use crate::types as ty; -use crate::values as v; -use rustc_hir::def_id::DefId; -use rustc_middle::mir; -use rustc_middle::ty as mir_ty; -use rustc_middle::ty::{ConstKind, Ty, TyKind}; -use std::iter::zip; - -/// Translate a typed constant value (either a bool, a char or an integer). -fn translate_constant_integer_like_value( - ty: &ty::ETy, - scalar: &mir::interpret::Scalar, -) -> v::Literal { - trace!(); - // The documentation explicitly says not to match on a scalar. - // We match on the type and convert the value following this, - // by calling the appropriate `to_*` method. - match ty { - ty::Ty::Literal(ty::LiteralTy::Bool) => v::Literal::Bool(scalar.to_bool().unwrap()), - ty::Ty::Literal(ty::LiteralTy::Char) => v::Literal::Char(scalar.to_char().unwrap()), - ty::Ty::Literal(ty::LiteralTy::Integer(i)) => v::Literal::Scalar(match i { - ty::IntegerTy::Isize => { - // This is a bit annoying: there is no - // `to_isize`. For now, we make the hypothesis - // that isize is an int64 - assert!(std::mem::size_of::() == 8); - v::ScalarValue::Isize(scalar.to_i64().unwrap()) - } - ty::IntegerTy::Usize => { - // Same as above for usize. - assert!(std::mem::size_of::() == 8); - v::ScalarValue::Usize(scalar.to_u64().unwrap()) - } - ty::IntegerTy::I8 => v::ScalarValue::I8(scalar.to_i8().unwrap()), - ty::IntegerTy::U8 => v::ScalarValue::U8(scalar.to_u8().unwrap()), - ty::IntegerTy::I16 => v::ScalarValue::I16(scalar.to_i16().unwrap()), - ty::IntegerTy::U16 => v::ScalarValue::U16(scalar.to_u16().unwrap()), - ty::IntegerTy::I32 => v::ScalarValue::I32(scalar.to_i32().unwrap()), - ty::IntegerTy::U32 => v::ScalarValue::U32(scalar.to_u32().unwrap()), - ty::IntegerTy::I64 => v::ScalarValue::I64(scalar.to_i64().unwrap()), - ty::IntegerTy::U64 => v::ScalarValue::U64(scalar.to_u64().unwrap()), - ty::IntegerTy::I128 => v::ScalarValue::I128(scalar.to_i128().unwrap()), - ty::IntegerTy::U128 => v::ScalarValue::U128(scalar.to_u128().unwrap()), - }), - _ => { - // The remaining types should not be used for constants, - // or should have been filtered by the caller. - error!("unexpected type: {:?}", ty); - unreachable!(); - } - } -} +use crate::types::*; +use crate::values::*; +use hax_frontend_exporter as hax; impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { - /// Translate the type of a [mir::interpret::ConstValue::Scalar] value : - /// Either a bool, a char, an integer, an enumeration ADT, an empty tuple or a static reference. - fn translate_constant_scalar_type(&mut self, ty: &TyKind) -> ty::ETy { - match ty { - TyKind::Bool => ty::Ty::Literal(ty::LiteralTy::Bool), - TyKind::Char => ty::Ty::Literal(ty::LiteralTy::Char), - TyKind::Int(int_ty) => match int_ty { - mir_ty::IntTy::Isize => { - ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::Isize)) - } - mir_ty::IntTy::I8 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::I8)), - mir_ty::IntTy::I16 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::I16)), - mir_ty::IntTy::I32 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::I32)), - mir_ty::IntTy::I64 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::I64)), - mir_ty::IntTy::I128 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::I128)), - }, - TyKind::Uint(uint_ty) => match uint_ty { - mir_ty::UintTy::Usize => { - ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::Usize)) - } - mir_ty::UintTy::U8 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::U8)), - mir_ty::UintTy::U16 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::U16)), - mir_ty::UintTy::U32 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::U32)), - mir_ty::UintTy::U64 => ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::U64)), - mir_ty::UintTy::U128 => { - ty::Ty::Literal(ty::LiteralTy::Integer(ty::IntegerTy::U128)) - } - }, - TyKind::Adt(adt_def, substs) => { - assert!(substs.is_empty()); - // It seems we can have ADTs when there is only one - // variant, and this variant doesn't take parameters. - // Retrieve the definition. - let id = self.translate_type_id(adt_def.did()); - ty::Ty::Adt(id, Vec::new(), Vec::new(), Vec::new()) - } - TyKind::Tuple(substs) => { - // There can be tuple([]) for unit - assert!(substs.is_empty()); - ty::Ty::Adt(ty::TypeId::Tuple, Vec::new(), Vec::new(), Vec::new()) - } - // Only accept scalars that are shared references with erased regions : it's a static. - TyKind::Ref(region, ref_ty, mir::Mutability::Not) => match region.kind() { - mir_ty::RegionKind::ReErased => ty::Ty::Ref( - ty::ErasedRegion::Erased, - Box::new(self.translate_constant_scalar_type(ref_ty.kind())), - ty::RefKind::Shared, - ), - _ => unreachable!(), - }, - TyKind::Float(_) => { - // We don't support floating point numbers: - // this should have been detected and eliminated before. - unreachable!(); - } - _ => { - // The remaining types should not be used for constants, or - // should have been filtered by the caller. - error!("unexpected type: {:?}", ty); - unreachable!(); - } - } - } - - /// Translate a parameter substitution. - /// - /// The regions parameters are expected to have been erased. - fn translate_subst_with_erased_regions( + fn translate_constant_literal_to_raw_constant_expr( &mut self, - substs: &rustc_middle::ty::List>, - ) -> Result> { - let mut t_args_tys = Vec::new(); - - for param in substs.iter() { - t_args_tys.push(self.translate_ety(¶m)?); - } - Ok(t_args_tys) - } - - /// Translate the type of a [mir::interpret::ConstValue::ByRef] value. - /// Currently, it should be a tuple. - fn translate_constant_reference_type(&mut self, ty: &TyKind<'tcx>) -> ty::ETy { - // Match on the type to destructure - match ty { - TyKind::Tuple(substs) => { - // Here, the substitution only contains types (no regions) - let type_params = self.translate_subst_with_erased_regions(substs).unwrap(); - trace!("{:?}", type_params); - let field_tys = type_params.into_iter().collect(); - ty::Ty::Adt(ty::TypeId::Tuple, Vec::new(), field_tys, Vec::new()) - } - TyKind::Adt(_, _) => { - // Following tests, it seems rustc doesn't introduce constants - // references when initializing ADTs, only when initializing tuples. - // Anyway, our `OperandConstantValue` handles all cases so updating - // the code to handle ADTs in a general manner wouldn't be a - // problem. - unreachable!("unexpected ADT type: {:?}", ty); - } - _ => { - // The remaining types should not be used for constants, or - // should have been filtered by the caller. - unreachable!("unexpected type: {:?}", ty); - } - } - } - - /// Translate a constant typed by [translate_constant_scalar_type]. - fn translate_constant_scalar_value( - &mut self, - llbc_ty: &ty::ETy, - scalar: &mir::interpret::Scalar, - ) -> e::OperandConstantValue { - trace!("{:?}", scalar); - - // The documentation explicitly says not to match on a scalar. - // A constant operand scalar is usually an instance of a primitive type - // (bool, char, integer...). However, it may also be an instance of a - // degenerate ADT or tuple (if an ADT has only one variant and no fields, - // it is a constant, and unit is encoded by MIR as a 0-tuple). - match llbc_ty { - ty::Ty::Literal(ty::LiteralTy::Bool) - | ty::Ty::Literal(ty::LiteralTy::Char) - | ty::Ty::Literal(ty::LiteralTy::Integer(_)) => { - let v = translate_constant_integer_like_value(llbc_ty, scalar); - e::OperandConstantValue::Literal(v) - } - ty::Ty::Adt(ty::TypeId::Adt(id), region_tys, field_tys, cgs) => { - assert!(region_tys.is_empty()); - assert!(field_tys.is_empty()); - assert!(cgs.is_empty()); - - let def = self.t_ctx.type_defs.get(*id).unwrap(); - - // Check that there is only one variant, with no fields - // and no parameters. Construct the value at the same time. - assert!(def.type_params.is_empty()); - let variant_id = match &def.kind { - ty::TypeDeclKind::Enum(variants) => { - assert!(variants.len() == 1); - Option::Some(ty::VariantId::ZERO) - } - ty::TypeDeclKind::Struct(_) => Option::None, - ty::TypeDeclKind::Opaque => { - unreachable!("Can't analyze a constant value built from an opaque type") + span: rustc_span::Span, + v: &hax::ConstantLiteral, + ) -> Result { + let lit = match v { + hax::ConstantLiteral::ByteStr(..) => { + error_or_panic!(self, span, "byte str constants are not supported yet"); + } + hax::ConstantLiteral::Char(c) => Literal::Char(*c), + hax::ConstantLiteral::Bool(b) => Literal::Bool(*b), + hax::ConstantLiteral::Int(i) => { + use hax::ConstantInt; + let scalar = match i { + ConstantInt::Int(v, int_type) => { + use hax::IntTy; + match int_type { + IntTy::Isize => ScalarValue::Isize(*v as i64), + IntTy::I8 => ScalarValue::I8(*v as i8), + IntTy::I16 => ScalarValue::I16(*v as i16), + IntTy::I32 => ScalarValue::I32(*v as i32), + IntTy::I64 => ScalarValue::I64(*v as i64), + IntTy::I128 => ScalarValue::I128(*v), + } } - }; - e::OperandConstantValue::Adt(variant_id, Vec::new()) - } - ty::Ty::Adt(ty::TypeId::Tuple, region_tys, field_tys, cgs) => { - assert!(region_tys.is_empty()); - assert!(field_tys.is_empty()); - assert!(cgs.is_empty()); - e::OperandConstantValue::Adt(Option::None, Vec::new()) - } - ty::Ty::Ref(ty::ErasedRegion::Erased, _, ty::RefKind::Shared) => match scalar { - mir::interpret::Scalar::Ptr(p, _) => { - match self.t_ctx.tcx.global_alloc(p.provenance) { - mir::interpret::GlobalAlloc::Static(s) => { - let id = self.translate_global_decl_id(s); - e::OperandConstantValue::StaticId(id) + ConstantInt::Uint(v, int_type) => { + use hax::UintTy; + match int_type { + UintTy::Usize => ScalarValue::Usize(*v as u64), + UintTy::U8 => ScalarValue::U8(*v as u8), + UintTy::U16 => ScalarValue::U16(*v as u16), + UintTy::U32 => ScalarValue::U32(*v as u32), + UintTy::U64 => ScalarValue::U64(*v as u64), + UintTy::U128 => ScalarValue::U128(*v), } - _ => unreachable!( - "Expected static pointer, got {:?}", - self.t_ctx.tcx.global_alloc(p.provenance) - ), } - } - _ => unreachable!("Expected static pointer, got {:?}", scalar), - }, - _ => { - // The remaining types should not be used for constants - unreachable!("unexpected type: {:?}, for scalar: {:?}", llbc_ty, scalar); - } - } - } - - /// Translate a constant typed by [translate_constant_reference_type]. - /// This should always be a tuple. - fn translate_constant_reference_value( - &mut self, - llbc_ty: &ty::ETy, - mir_ty: &Ty<'tcx>, // TODO: remove? - value: &mir::interpret::ConstValue<'tcx>, - ) -> e::OperandConstantValue { - trace!(); - - let tcx = self.t_ctx.tcx; - - // We use [try_destructure_mir_constant] to destructure the constant - // We need a param_env: we use the function def id as a dummy id... - let param_env = tcx.param_env(self.def_id); - // We have to clone some values: it is a bit annoying, but I don't - // manage to get the lifetimes working otherwise... - let cvalue = rustc_middle::mir::ConstantKind::Val(*value, *mir_ty); - let param_env_and_const = rustc_middle::ty::ParamEnvAnd { - param_env, - value: cvalue, - }; - - let dc = tcx - .try_destructure_mir_constant(param_env_and_const) - .unwrap(); - trace!("{:?}", dc); - - // Iterate over the fields - assert!(dc.variant.is_none()); - - // Below: we are mutually recursive with [translate_constant_kind], - // which takes a [ConstantKind] as input (see `cvalue` above), but it should be - // ok because we call it on a strictly smaller value. - let fields: Vec<(ty::ETy, e::OperandConstantValue)> = dc - .fields - .iter() - .map(|f| self.translate_constant_kind(f)) - .collect(); - - // Sanity check - match llbc_ty { - ty::Ty::Adt(ty::TypeId::Tuple, regions, fields_tys, cgs) => { - assert!(regions.is_empty()); - assert!(zip(&fields, fields_tys).all(|(f, ty)| &f.0 == ty)); - assert!(cgs.is_empty()); + }; + Literal::Scalar(scalar) } - _ => unreachable!("Expected a tuple, got {:?}", mir_ty), }; - - let fields: Vec = fields.into_iter().map(|f| f.1).collect(); - e::OperandConstantValue::Adt(Option::None, fields) - } - - /// Translate a [mir::interpret::ConstValue] - fn translate_const_value( - &mut self, - llbc_ty: &ty::ETy, - mir_ty: &Ty<'tcx>, // TODO: remove? - val: &mir::interpret::ConstValue<'tcx>, - ) -> e::OperandConstantValue { - trace!("{:?}", val); - match val { - mir::interpret::ConstValue::Scalar(scalar) => { - self.translate_constant_scalar_value(llbc_ty, scalar) - } - mir::interpret::ConstValue::ByRef { .. } => { - self.translate_constant_reference_value(llbc_ty, mir_ty, val) - } - mir::interpret::ConstValue::Slice { .. } => unimplemented!(), - mir::interpret::ConstValue::ZeroSized { .. } => { - // Should be unit - assert!(llbc_ty.is_unit()); - e::OperandConstantValue::Adt(None, Vec::new()) - } - } - } - - /// This function translates a constant id, under the condition that the - /// constants are extracted at the top level. - fn translate_constant_id_as_top_level( - &mut self, - rid: DefId, - mir_ty: &mir_ty::Ty<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - // Sanity check - assert!(extract_constants_at_top_level(self.t_ctx.mir_level)); - - // Lookup the constant identifier and refer to it. - let id = self.translate_global_decl_id(rid); - let ty = self.translate_ety(mir_ty).unwrap(); - (ty, e::OperandConstantValue::ConstantId(id)) - } - - fn translate_const_kind_unevaluated( - &mut self, - mir_ty: &mir_ty::Ty<'tcx>, - ucv: &rustc_middle::mir::UnevaluatedConst<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - // Two cases: - // - if we extract the constants at top level, we lookup the constant - // identifier and refer to it - // - otherwise, we evaluate the constant and insert it in place - if extract_constants_at_top_level(self.t_ctx.mir_level) { - self.translate_constant_id_as_top_level(ucv.def, mir_ty) - } else { - // Evaluate the constant. - // We need a param_env: we use the function def id as a dummy id... - let tcx = self.t_ctx.tcx; - let param_env = tcx.param_env(self.def_id); - let cv = tcx.const_eval_resolve(param_env, *ucv, None).unwrap(); - let llbc_ty = self.translate_ety(mir_ty).unwrap(); - let v = self.translate_const_value(&llbc_ty, mir_ty, &cv); - (llbc_ty, v) - } + Ok(RawConstantExpr::Literal(lit)) } - pub(crate) fn translate_const_kind( + pub(crate) fn translate_constant_expr_kind_to_constant_expr( &mut self, - constant: rustc_middle::ty::Const<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - match constant.kind() { - ConstKind::Value(v) => { - // The value is a [ValTree]. - // For now, we only imlement support for a limited subset of the cases - - // there are many cases for which I don't know in which situations they - // happen. - - // We only support integers and scalars - let ty = self.translate_ety(&constant.ty()).unwrap(); - let v = match v { - mir_ty::ValTree::Leaf(v) => match ty.as_literal() { - ty::LiteralTy::Integer(int_ty) => { - if int_ty.is_signed() { - let v = v.try_to_int(v.size()).unwrap(); - v::Literal::Scalar(v::ScalarValue::from_int(*int_ty, v).unwrap()) - } else { - let v = v.try_to_uint(v.size()).unwrap(); - v::Literal::Scalar(v::ScalarValue::from_uint(*int_ty, v).unwrap()) - } - } - ty::LiteralTy::Bool => { - let v = v.try_to_bool().unwrap(); - v::Literal::Bool(v) - } - ty::LiteralTy::Char => unimplemented!(), - }, - mir_ty::ValTree::Branch(_) => { - // In practice I don't know when this is used - unimplemented!() - } + span: rustc_span::Span, + ty: &hax::Ty, + v: &hax::ConstantExprKind, + ) -> Result { + use hax::ConstantExprKind; + let erase_regions = true; + let value = match v { + ConstantExprKind::Literal(lit) => { + self.translate_constant_literal_to_raw_constant_expr(span, lit)? + } + ConstantExprKind::Adt { + info: _, + vid, + fields, + } => { + let fields: Vec = fields + .iter() + .map(|f| self.translate_constant_expr_to_constant_expr(span, &f.value)) + .try_collect()?; + let vid = vid.map(VariantId::Id::new); + RawConstantExpr::Adt(vid, fields) + } + ConstantExprKind::Array { .. } => { + error_or_panic!(self, span, "array constants are not supported yet") + } + ConstantExprKind::Tuple { fields } => { + let fields: Vec = fields + .iter() + .map(|f| self.translate_constant_expr_to_constant_expr(span, f)) + .try_collect()?; + RawConstantExpr::Adt(Option::None, fields) + } + ConstantExprKind::TraitConst { + impl_source, + substs, + name, + } => { + let trait_ref = + self.translate_trait_impl_source(span, erase_regions, impl_source)?; + // The trait ref should be Some(...): trait markers (that we + // may eliminate) don't have constants. + let trait_ref = trait_ref.unwrap(); + + let (regions, types, const_generics) = + self.translate_substs(span, erase_regions, None, substs)?; + let generics = GenericArgs { + regions, + types, + const_generics, + trait_refs: Vec::new(), }; - (ty, e::OperandConstantValue::Literal(v)) + let name = TraitItemName(name.clone()); + RawConstantExpr::TraitConst(trait_ref, generics, name) + } + ConstantExprKind::GlobalName { id } => { + RawConstantExpr::Global(self.translate_global_decl_id(id.rust_def_id.unwrap())) + } + ConstantExprKind::Borrow(be) => { + let be = self.translate_constant_expr_to_constant_expr(span, be)?; + RawConstantExpr::Ref(Box::new(be)) + } + ConstantExprKind::ConstRef { id } => { + let var_id = self.const_generic_vars_map.get(&id.index).unwrap(); + RawConstantExpr::Var(var_id) + } + ConstantExprKind::FnPtr(fn_id, substs, trait_refs, trait_info) => { + use crate::translate_functions_to_ullbc::SubstFunIdOrPanic; + let erase_regions = true; // TODO: not sure + let fn_id = self.translate_fun_decl_id_with_args( + span, + erase_regions, + fn_id, + substs, + None, + trait_refs, + trait_info, + )?; + let SubstFunIdOrPanic::Fun(fn_id) = fn_id else { unreachable!() }; + RawConstantExpr::FnPtr(fn_id.func) + } + ConstantExprKind::Todo(msg) => { + // Case not yet handled by hax + error_or_panic!(self, span, format!("Unsupported constant: {:?}", msg)) } - ConstKind::Expr(_) => { - unimplemented!(); - } - ConstKind::Unevaluated(ucv) => { - // Two cases: - // - if we extract the constants at top level, we lookup the constant - // identifier and refer to it - // - otherwise, we evaluate the constant and insert it in place - if extract_constants_at_top_level(self.t_ctx.mir_level) { - self.translate_constant_id_as_top_level(ucv.def, &constant.ty()) - } else { - // TODO: we can't call [translate_const_kind_unevaluated]: - // the types don't match. - // We could use [TyCtxt.const_eval_resolve_for_typeck] - // to get a [ValTree] - unimplemented!(); - } - } - ConstKind::Param(cp) => { - let ty = self.translate_ety(&constant.ty()).unwrap(); - let cg_id = self.const_generic_vars_map.get(&cp.index).unwrap(); - (ty, e::OperandConstantValue::Var(*cg_id)) - } - ConstKind::Infer(_) - | ConstKind::Bound(_, _) - | ConstKind::Placeholder(_) - | ConstKind::Error(_) => { - unreachable!("Unexpected: {:?}", constant); - } - } - } + }; - pub(crate) fn translate_const_kind_as_const_generic( - &mut self, - constant: rustc_middle::ty::Const<'tcx>, - ) -> ty::ConstGeneric { - let (ty, c) = self.translate_const_kind(constant); - assert!(ty.is_literal()); - match c { - e::OperandConstantValue::Literal(v) => ty::ConstGeneric::Value(v), - e::OperandConstantValue::Adt(..) => unreachable!(), - e::OperandConstantValue::ConstantId(v) => ty::ConstGeneric::Global(v), - e::OperandConstantValue::StaticId(_) => unreachable!(), - e::OperandConstantValue::Var(v) => ty::ConstGeneric::Var(v), - } + let ty = self.translate_ty(span, erase_regions, ty)?; + Ok(ConstantExpr { value, ty }) } - /// Translate a constant which may not be yet evaluated. - pub(crate) fn translate_constant_kind( + /// Remark: [hax::ConstantExpr] contains span information, but it is often + /// the default span (i.e., it is useless), hence the additional span argument. + pub(crate) fn translate_constant_expr_to_constant_expr( &mut self, - constant: &rustc_middle::mir::ConstantKind<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - trace!("{:?}", constant); - - match constant { - // This is the "normal" constant case - // TODO: this changed when we updated from Nightly 2022-01-29 to - // Nightly 2022-09-19, and the `Val` case used to be ignored. - // SH: I'm not sure which corresponds to what (the documentation - // is not super clear). - mir::ConstantKind::Ty(c) => self.translate_const_kind(*c), - // I'm not sure what this is about: the documentation is weird. - mir::ConstantKind::Val(cv, ty) => { - trace!("cv: {:?}, ty: {:?}", cv, ty); - self.translate_evaluated_operand_constant(ty, cv) - } - rustc_middle::mir::ConstantKind::Unevaluated(ucv, mir_ty) => { - self.translate_const_kind_unevaluated(mir_ty, ucv) - } - } + span: rustc_span::Span, + v: &hax::ConstantExpr, + ) -> Result { + self.translate_constant_expr_kind_to_constant_expr(span, &v.ty, &v.contents) } - pub(crate) fn translate_evaluated_operand_constant( + /// Remark: [hax::ConstantExpr] contains span information, but it is often + /// the default span (i.e., it is useless), hence the additional span argument. + pub(crate) fn translate_constant_expr_to_const_generic( &mut self, - ty: &Ty<'tcx>, - val: &mir::interpret::ConstValue<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - let llbc_ty = self.translate_ety(ty).unwrap(); - let im_val = self.translate_const_value(&llbc_ty, ty, val); - (llbc_ty, im_val) + span: rustc_span::Span, + v: &hax::ConstantExpr, + ) -> Result { + let value = self + .translate_constant_expr_to_constant_expr(span, v)? + .value; + match value { + RawConstantExpr::Literal(v) => Ok(ConstGeneric::Value(v)), + RawConstantExpr::Global(v) => Ok(ConstGeneric::Global(v)), + RawConstantExpr::Adt(..) + | RawConstantExpr::TraitConst { .. } + | RawConstantExpr::Ref(_) + | RawConstantExpr::FnPtr { .. } => { + error_or_panic!( + self, + span, + format!("Unexpected constant generic: {:?}", value) + ) + } + RawConstantExpr::Var(v) => Ok(ConstGeneric::Var(v)), + } } - /// Translate a constant which may not be yet evaluated. - pub(crate) fn translate_operand_constant( + /// Remark: [hax::ConstantExpr] contains span information, but it is often + /// the default span (i.e., it is useless), hence the additional span argument. + pub(crate) fn translate_constant_to_constant_expr( &mut self, - constant: &mir::Constant<'tcx>, - ) -> (ty::ETy, e::OperandConstantValue) { - trace!("{:?}", constant); - use std::ops::Deref; - let constant = &constant.deref(); - - self.translate_constant_kind(&constant.literal) + span: rustc_span::Span, + v: &hax::Constant, + ) -> Result { + self.translate_constant_expr_to_constant_expr(span, &v.literal.constant_kind) } } diff --git a/charon/src/translate_crate_to_ullbc.rs b/charon/src/translate_crate_to_ullbc.rs index d94d5014..de8d0fca 100644 --- a/charon/src/translate_crate_to_ullbc.rs +++ b/charon/src/translate_crate_to_ullbc.rs @@ -1,32 +1,60 @@ +use crate::cli_options::CliOpts; use crate::get_mir::{extract_constants_at_top_level, MirLevel}; use crate::meta; -use crate::names::{hir_item_to_name, item_def_id_to_name}; -use crate::reorder_decls as rd; use crate::translate_ctx::*; use crate::translate_functions_to_ullbc; use crate::types as ty; use crate::ullbc_ast as ast; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; use linked_hash_set::LinkedHashSet; use rustc_hir::{Defaultness, ImplItem, ImplItemKind, Item, ItemKind}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { fn register_local_hir_impl_item(&mut self, _top_item: bool, impl_item: &ImplItem) { // TODO: make a proper error message assert!(impl_item.defaultness == Defaultness::Final); + let def_id = impl_item.owner_id.to_def_id(); + // Match on the impl item kind match &impl_item.kind { - ImplItemKind::Const(_, _) => unimplemented!(), + ImplItemKind::Const(_, _) => { + // Can happen in traits: + // ``` + // trait Foo { + // const C : usize; + // } + // + // impl Foo for Bar { + // const C = 32; // HERE + // } + // ``` + let _ = self.translate_global_decl_id(def_id); + } ImplItemKind::Type(_) => { - // Note sure what to do with associated types yet - unimplemented!(); + // Trait type: + // ``` + // trait Foo { + // type T; + // } + // + // impl Foo for Bar { + // type T = bool; // HERE + // } + // ``` + // + // Do nothing for now: we won't generate a top-level definition + // for this, and handle it when translating the trait implementation + // this item belongs to. + let tcx = self.tcx; + assert!(tcx.associated_item(def_id).trait_item_def_id.is_some()); } ImplItemKind::Fn(_, _) => { - let local_id = impl_item.owner_id.to_def_id().as_local().unwrap(); - let _ = self.translate_fun_decl_id(local_id.to_def_id()); + let _ = self.translate_fun_decl_id(def_id); } } } @@ -52,19 +80,22 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { // item (not an item transitively reachable from an item which is not // opaque) and inside an opaque module (or sub-module), we ignore it. if top_item { - match hir_item_to_name(self.tcx, item) { + match self.hir_item_to_name(item) { Option::None => { // This kind of item is to be ignored + trace!("Ignoring {:?} (ignoring item kind)", item.item_id()); return; } Option::Some(item_name) => { if self.crate_info.is_opaque_decl(&item_name) { + trace!("Ignoring {:?} (marked as opaque)", item.item_id()); return; } // Continue } } } + trace!("Registering {:?}", item.item_id()); // Case disjunction on the item kind. let def_id = item.owner_id.to_def_id(); @@ -73,18 +104,38 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { // We ignore the type aliases - it seems they are inlined } ItemKind::OpaqueTy(_) => unimplemented!(), - ItemKind::Union(_, _) => unimplemented!(), - ItemKind::Enum(_, _) | ItemKind::Struct(_, _) => { + ItemKind::Union(..) => unimplemented!(), + ItemKind::Enum(..) | ItemKind::Struct(_, _) => { let _ = self.translate_type_decl_id(def_id); } ItemKind::Fn(_, _, _) => { let _ = self.translate_fun_decl_id(def_id); } - ItemKind::Const(_, _) | ItemKind::Static(_, _, _) => { - if extract_constants_at_top_level(self.mir_level) { - let _ = self.translate_global_decl_id(def_id); - } else { - // Avoid registering globals in optimized MIR (they will be inlined) + ItemKind::Trait(..) => { + let _ = self.translate_trait_decl_id(def_id); + // We don't need to explore the associated items: we will + // explore them when translating the trait + } + ItemKind::Const(..) | ItemKind::Static(..) => { + // We ignore the anonymous constants, which are introduced + // by the Rust compiler: those constants will be inlined in the + // function bodies. + // + // Important: if we try to retrieve the MIR of anonymous constants, + // it will steal the MIR of the bodies of the functions in which + // they appear. + // + // Also note that this is the only place where we need to check + // if an item is an anonymous constant: when translating the bodies, + // as the anonymous constants are inlined in those bodies, they + // disappear completely. + let trans_id: hax::DefId = def_id.sinto(&self.hax_state); + if !trans_id.is_anon_const() { + if extract_constants_at_top_level(self.mir_level) { + let _ = self.translate_global_decl_id(def_id); + } else { + // Avoid registering globals in optimized MIR (they will be inlined) + } } } @@ -93,6 +144,11 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { // Sanity checks - TODO: remove? translate_functions_to_ullbc::check_impl_item(impl_block); + // If this is a trait implementation, register it + if self.tcx.trait_id_of_impl(def_id).is_some() { + let _ = self.translate_trait_impl_id(def_id); + } + // Explore the items let hir_map = self.tcx.hir(); for impl_item_ref in impl_block.items { @@ -117,13 +173,12 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { // to check that all the opaque modules given as arguments actually // exist trace!("{:?}", def_id); - let module_name = item_def_id_to_name(self.tcx, def_id); let opaque = self.id_is_opaque(def_id); if opaque { // Ignore - trace!("Ignoring module [{}] because marked as opaque", module_name); + trace!("Ignoring module [{:?}] because marked as opaque", def_id); } else { - trace!("Diving into module [{}]", module_name); + trace!("Diving into module [{:?}]", def_id); let hir_map = self.tcx.hir(); for item_id in module.item_ids { // Lookup and register the item @@ -132,6 +187,11 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { } } } + ItemKind::Macro(..) => { + // We ignore macro definitions. Note that when a macro is applied, + // we directly see the result of its expansion by the Rustc compiler, + // which is why we don't have to care about them. + } _ => { unimplemented!("{:?}", item.kind); } @@ -142,17 +202,28 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Translate all the declarations in the crate. pub fn translate<'tcx, 'ctx>( crate_info: CrateInfo, - sess: &'ctx Session, + options: &CliOpts, + session: &'ctx Session, tcx: TyCtxt<'tcx>, mir_level: MirLevel, ) -> TransCtx<'tcx, 'ctx> { + let hax_state = hax::state::State::new( + tcx, + hax::options::Options { + inline_macro_calls: Vec::new(), + }, + ); let mut ctx = TransCtx { - sess, + session, tcx, + hax_state, mir_level, crate_info, + continue_on_failure: !options.panic_on_error, + error_count: 0, + no_code_duplication: options.no_code_duplication, all_ids: LinkedHashSet::new(), - stack: LinkedHashSet::new(), + stack: BTreeSet::new(), file_to_id: HashMap::new(), id_to_file: HashMap::new(), real_file_counter: meta::LocalFileId::Generator::new(), @@ -163,17 +234,30 @@ pub fn translate<'tcx, 'ctx>( fun_defs: ast::FunDeclId::Map::new(), global_id_map: ast::GlobalDeclId::MapGenerator::new(), global_defs: ast::GlobalDeclId::Map::new(), + trait_decl_id_map: ast::TraitDeclId::MapGenerator::new(), + trait_decls: ast::TraitDeclId::Map::new(), + trait_impl_id_map: ast::TraitImplId::MapGenerator::new(), + trait_impl_id_to_def_id: HashMap::new(), + trait_impls: ast::TraitImplId::Map::new(), }; // First push all the items in the stack of items to translate. // - // The way rustc works is as follows: - // - we call it on the root of the crate (for instance "main.rs"), and it - // explores all the files from there (typically listed through statements - // of the form "mod MODULE_NAME") - // - the other files in the crate are Module items in the HIR graph + // We explore the crate by starting with the root module. + // + // Remark: It is important to do like this (and not iterate over all the items) + // if we want the "opaque" options (to ignore parts of the crate) to work. + // For instance, if we mark "foo::bar" as opaque, we will ignore the module + // "foo::bar" altogether (we will not even look at the items). + // If we look at the items, we risk registering items just by looking + // at their name. For instance, if we check the item `foo::bar::{foo::bar::Ty}::f`, + // then by converting the Rust name to an LLBC name, we will actually register + // the name "foo::bar::Ty" (so that we can generate the "impl" path element + // `{foo::bar::Ty}`), which means we will register the item `foo::bar::Ty`. + // We could make the name translation work differently if we do have to + // explore all the items in the crate. let hir = tcx.hir(); - for item_id in hir.items() { + for item_id in hir.root_module().item_ids { let item_id = item_id.hir_id(); let node = hir.find(item_id).unwrap(); let item = match node { @@ -183,6 +267,8 @@ pub fn translate<'tcx, 'ctx>( ctx.register_local_hir_item(true, item); } + trace!("Stack after we explored the crate:\n{:?}", &ctx.stack); + // Translate. // // For as long as the stack of items to translate is not empty, we pop the top item @@ -192,11 +278,14 @@ pub fn translate<'tcx, 'ctx>( // Note that the order in which we translate the definitions doesn't matter: // we never need to lookup a translated definition, and only use the map // from Rust ids to translated ids. - while let Some(id) = ctx.stack.pop_front() { + while let Some(id) = ctx.stack.pop_first() { + trace!("About to translate id: {:?}", id); match id { - rd::AnyDeclId::Type(id) => ctx.translate_type(id), - rd::AnyDeclId::Fun(id) => ctx.translate_function(id), - rd::AnyDeclId::Global(id) => ctx.translate_global(id), + OrdRustId::Type(id) => ctx.translate_type(id), + OrdRustId::Fun(id) | OrdRustId::ConstFun(id) => ctx.translate_function(id), + OrdRustId::Global(id) => ctx.translate_global(id), + OrdRustId::TraitDecl(id) => ctx.translate_trait_decl(id), + OrdRustId::TraitImpl(id) => ctx.translate_trait_impl(id), } } diff --git a/charon/src/translate_ctx.rs b/charon/src/translate_ctx.rs index c7020653..42bd94ed 100644 --- a/charon/src/translate_ctx.rs +++ b/charon/src/translate_ctx.rs @@ -1,25 +1,63 @@ //! The translation contexts. - -#![allow(dead_code)] +use crate::common::TAB_INCR; use crate::formatter::Formatter; +use crate::gast; use crate::get_mir::MirLevel; +use crate::llbc_ast; use crate::meta; use crate::meta::{FileId, FileName, LocalFileId, Meta, VirtualFileId}; use crate::names::Name; -use crate::reorder_decls::{AnyRustId, AnyTransId}; -use crate::types as ty; -use crate::types::LiteralTy; +use crate::reorder_decls::AnyTransId; +use crate::translate_predicates::NonLocalTraitClause; +use crate::types::*; +use crate::ullbc_ast; use crate::ullbc_ast as ast; -use crate::values as v; +use crate::values::*; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; +use im::OrdMap; use linked_hash_set::LinkedHashSet; +use macros::VariantIndexArity; use rustc_hir::def_id::DefId; -use rustc_index::IndexVec; -use rustc_middle::mir; -use rustc_middle::mir::BasicBlock; -use rustc_middle::mir::{SourceInfo, SourceScope, SourceScopeData}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use std::collections::{HashMap, HashSet}; +use std::cmp::{Ord, Ordering, PartialOrd}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt; + +/// Macro to either panic or return on error, depending on the CLI options +macro_rules! error_or_panic { + ($ctx:ident, $span: ident, $msg: expr) => {{ + $ctx.span_err($span, &$msg); + if $ctx.continue_on_failure() { + let e = crate::common::Error { + span: $span, + msg: $msg.to_string(), + }; + return (Err(e)); + } else { + panic!("{}", $msg); + } + }}; +} +pub(crate) use error_or_panic; + +/// Custom assert to either panic or return an error +macro_rules! error_assert { + ($ctx:ident, $span: ident, $b: expr) => { + if !$b { + let msg = format!("assertion failure: {:?}", stringify!($b)); + $ctx.span_err($span, &msg); + if $ctx.continue_on_failure() { + let e = crate::common::Error { span: $span, msg }; + return (Err(e)); + } else { + panic!("{}", msg); + } + } + }; +} +pub(crate) use error_assert; pub struct CrateInfo { pub crate_name: String, @@ -31,34 +69,94 @@ impl CrateInfo { name.is_in_modules(&self.crate_name, &self.opaque_mods) } - fn is_transparent_decl(&self, name: &Name) -> bool { + #[allow(dead_code)] + pub(crate) fn is_transparent_decl(&self, name: &Name) -> bool { !self.is_opaque_decl(name) } } +/// We use a special type to store the Rust identifiers in the stack, to +/// make sure we translate them in a specific order (top-level constants +/// before constant functions before functions...). This allows us to +/// avoid stealing issues when looking up the MIR bodies. +#[derive(Clone, Copy, Debug, Eq, PartialEq, VariantIndexArity)] +pub enum OrdRustId { + Global(DefId), + ConstFun(DefId), + TraitDecl(DefId), + TraitImpl(DefId), + Fun(DefId), + Type(DefId), +} + +impl OrdRustId { + fn get_id(&self) -> DefId { + match self { + OrdRustId::Global(id) + | OrdRustId::ConstFun(id) + | OrdRustId::TraitDecl(id) + | OrdRustId::TraitImpl(id) + | OrdRustId::Fun(id) + | OrdRustId::Type(id) => *id, + } + } +} + +impl PartialOrd for OrdRustId { + fn partial_cmp(&self, other: &OrdRustId) -> Option { + let (vid0, _) = self.variant_index_arity(); + let (vid1, _) = other.variant_index_arity(); + if vid0 != vid1 { + Option::Some(vid0.cmp(&vid1)) + } else { + let id0 = self.get_id(); + let id1 = other.get_id(); + Option::Some(id0.cmp(&id1)) + } + } +} + +impl Ord for OrdRustId { + fn cmp(&self, other: &OrdRustId) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + /// Translation context containing the top-level definitions. pub struct TransCtx<'tcx, 'ctx> { /// The compiler session - pub sess: &'ctx Session, - /// + pub session: &'ctx Session, + /// The Rust compiler type context pub tcx: TyCtxt<'tcx>, + /// The Hax context + pub hax_state: hax::State, (), (), ()>, /// The level at which to extract the MIR pub mir_level: MirLevel, /// pub crate_info: CrateInfo, - /// All the ids + /// Do not abort on the first error and attempt to extract as much as possible. + pub continue_on_failure: bool, + /// The number of errors encountered so far. + pub error_count: usize, + /// Error out if some code ends up being duplicated by the control-flow + /// reconstruction (note that because several patterns in a match may lead + /// to the same branch, it is node always possible not to duplicate code). + pub no_code_duplication: bool, + /// All the ids, in the order in which we encountered them pub all_ids: LinkedHashSet, - /// The declarations we came accross and which we haven't translated yet - pub stack: LinkedHashSet, + /// The declarations we came accross and which we haven't translated yet. + /// We use an ordered set to make sure we translate them in a specific + /// order (this avoids stealing issues when querying the MIR bodies). + pub stack: BTreeSet, /// File names to ids and vice-versa pub file_to_id: HashMap, pub id_to_file: HashMap, pub real_file_counter: LocalFileId::Generator, pub virtual_file_counter: VirtualFileId::Generator, /// The map from Rust type ids to translated type ids - pub type_id_map: ty::TypeDeclId::MapGenerator, + pub type_id_map: TypeDeclId::MapGenerator, /// The translated type definitions - pub type_defs: ty::TypeDecls, + pub type_defs: TypeDecls, /// The map from Rust function ids to translated function ids pub fun_id_map: ast::FunDeclId::MapGenerator, /// The translated function definitions @@ -67,48 +165,67 @@ pub struct TransCtx<'tcx, 'ctx> { pub global_id_map: ast::GlobalDeclId::MapGenerator, /// The translated global definitions pub global_defs: ast::GlobalDecls, + /// The map from Rust trait decl ids to translated trait decl ids + pub trait_decl_id_map: ast::TraitDeclId::MapGenerator, + /// The translated trait declarations + pub trait_decls: ast::TraitDecls, + /// The map from Rust trait impls ids to translated trait impls ids + pub trait_impl_id_map: ast::TraitImplId::MapGenerator, + pub trait_impl_id_to_def_id: HashMap, + /// The translated trait declarations + pub trait_impls: ast::TraitImpls, } /// A translation context for type/global/function bodies. /// Simply augments the [TransCtx] with local variables. /// -/// TODO: use other collections than `im::OrdMap`? (we don't need a O(1) clone -/// operation). -/// TODO: remove the borrow for the TransCtx, or make it a mutable borrow. +/// Remark: for now we don't really need to use collections from the [im] crate, +/// because we don't need the O(1) clone operation, but we may need it once we +/// implement support for universally quantified traits, where we might need +/// to be able to dive in/out of universal quantifiers. Also, it doesn't cost +/// us to use those collections. pub(crate) struct BodyTransCtx<'tcx, 'ctx, 'ctx1> { - /// This is used in very specific situations. + /// This is used in very specific situations. TODO: remove? pub def_id: DefId, /// The translation context containing the top-level definitions/ids. pub t_ctx: &'ctx mut TransCtx<'tcx, 'ctx1>, - /// Region counter - pub regions_counter: ty::RegionVarId::Generator, - /// The regions - TODO: rename to region_vars - pub region_vars: ty::RegionVarId::Vector, - // TODO: use the MapGenerator types + /// A hax state with an owner id + pub hax_state: hax::State, (), (), rustc_hir::def_id::DefId>, + /// The regions + pub region_vars: RegionId::Vector, /// The map from rust region to translated region indices - pub region_vars_map: im::OrdMap, ty::RegionVarId::Id>, - /// Id counter for the type variables - pub type_vars_counter: ty::TypeVarId::Generator, + pub region_vars_map: RegionId::MapGenerator, /// The type variables - pub type_vars: ty::TypeVarId::Vector, + pub type_vars: TypeVarId::Vector, /// The map from rust type variable indices to translated type variable /// indices. - pub type_vars_map: im::OrdMap, - /// Id counter for the variables - pub vars_counter: v::VarId::Generator, + pub type_vars_map: TypeVarId::MapGenerator, /// The "regular" variables - pub vars: v::VarId::Vector, + pub vars: VarId::Vector, /// The map from rust variable indices to translated variables indices. - pub vars_map: im::OrdMap, - /// Id counter for the const generic variables - pub const_generic_counter: ty::ConstGenericVarId::Generator, + pub vars_map: VarId::MapGenerator, /// The const generic variables - pub const_generic_vars: ty::ConstGenericVarId::Vector, + pub const_generic_vars: ConstGenericVarId::Vector, /// The map from rust const generic variables to translate const generic /// variable indices. - pub const_generic_vars_map: im::OrdMap, - /// Block id counter - pub blocks_counter: ast::BlockId::Generator, + pub const_generic_vars_map: ConstGenericVarId::MapGenerator, + /// A generator for trait instance ids. + /// We initialize it so that it generates ids for local clauses. + pub trait_instance_id_gen: Box TraitInstanceId>, + /// All the trait clauses accessible from the current environment + pub trait_clauses: OrdMap, + /// If [true] it means we are currently registering trait clauses in the + /// local context. As a consequence, we allow not solving all the trait + /// obligations, because the obligations for some clauses may be solved + /// by other clauses which have not been registered yet. + /// For this reason, we do the resolution in several passes. + pub registering_trait_clauses: bool, + /// + pub types_outlive: Vec, + /// + pub regions_outlive: Vec, + /// + pub trait_type_constraints: Vec, /// The translated blocks. We can't use `ast::BlockId::Vector` /// here because we might generate several fresh indices before actually /// adding the resulting blocks to the map. @@ -116,21 +233,74 @@ pub(crate) struct BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// The map from rust blocks to translated blocks. /// Note that when translating terminators like DropAndReplace, we might have /// to introduce new blocks which don't appear in the original MIR. - pub blocks_map: im::OrdMap, + pub blocks_map: ast::BlockId::MapGenerator, + /// + /// The stack of late-bound parameters (can only be lifetimes for now), which + /// use DeBruijn indices (the other parameters use free variables). + /// For explanations about what early-bound and late-bound parameters are, see: + /// https://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/ + /// https://smallcultfollowing.com/babysteps/blog/2013/11/04/intermingled-parameter-lists/ + /// + /// Remark: even though performance is not critical, the use of [im::Vec] allows + /// us to push/pop and access indexed elements with very good performance. + /// + /// **Important**: + /// ============== + /// The Rust compiler uses De Bruijn indices to identify the *group* of + /// universally quantified variables, and variable identifiers to identity + /// the variables inside the group. + /// + /// For instance, we have the following: + /// ```text + /// we compute the De Bruijn indices from here + /// VVVVVVVVVVVVVVVVVVVVVVV + /// fn f<'a, 'b>(x: for<'c> fn(&'a u8, &'b u16, &'c u32) -> u64) {} + /// ^^^^^^ ^^ ^ ^ ^ + /// | De Bruijn: 0 | | | + /// De Bruijn: 1 | | | + /// De Bruijn: 1 | De Bruijn: 0 + /// Var id: 0 | Var id: 0 + /// | + /// De Bruijn: 1 + /// Var id: 1 + /// ``` + /// + /// For this reason, we use a stack of vectors to store the bound variables. + pub bound_region_vars: im::Vector>, +} + +/// A formatting context for type/global/function bodies. +/// Simply augments the [TransCtx] with local variables. +/// +/// We can directly use the [TransCtx] and the [BodyTransCtx] for formatting. +/// However, sometimes we only have a [TransCtx] at our disposal when formatting +/// a definition. We could transform it into a [BodyTransCtx] by adding the +/// proper information, but this requires a mutable access to the [TransCtx]. +/// Instead, we can create a [BodyFormatCtx]. +pub(crate) struct BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + /// The translation context containing the top-level definitions/ids. + pub t_ctx: &'ctx TransCtx<'tcx, 'ctx1>, + pub generics: &'ctx GenericParams, + /// The "regular" variables + pub vars: &'ctx VarId::Vector, + /// + #[allow(dead_code)] + pub trait_clauses: &'ctx TraitClauseId::Vector, } impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { - /// Register the file containing a definition (rem.: we register the - /// file containing the definition itself, not its def ident). - fn translate_file_from_def_id(&mut self, def_id: DefId) -> FileId::Id { - let span = meta::get_rspan_from_def_id(self.tcx, def_id); - self.translate_file_from_span(span) + pub fn continue_on_failure(&self) -> bool { + self.continue_on_failure } - /// Register the file referenced by a span - fn translate_file_from_span(&mut self, span: rustc_span::Span) -> FileId::Id { - let filename = meta::get_filename_from_rspan(self.sess, span); - self.register_file(filename) + pub fn span_err(&mut self, span: rustc_span::Span, msg: &str) { + let msg = msg.to_string(); + self.session.span_err(span, msg); + self.increment_error_count(); + } + + fn increment_error_count(&mut self) { + self.error_count += 1; } /// Register a file if it is a "real" file and was not already registered @@ -158,20 +328,12 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { pub(crate) fn translate_meta_from_rid(&mut self, def_id: DefId) -> Meta { // Retrieve the span from the def id let rspan = meta::get_rspan_from_def_id(self.tcx, def_id); - + let rspan = rspan.sinto(&self.hax_state); self.translate_meta_from_rspan(rspan) } - pub fn translate_span(&mut self, rspan: rustc_span::Span) -> meta::Span { - // Retrieve the source map, which contains information about the source file: - // we need it to be able to interpret the span. - let source_map = self.sess.source_map(); - - // Find the source file and the span. - // It is very annoying: macros get expanded to statements whose spans refer - // to the file where the macro is defined, not the file where it is used. - let (beg, end) = source_map.is_valid_span(rspan).unwrap(); - let filename = meta::convert_filename(&beg.file.name); + pub fn translate_span(&mut self, rspan: hax::Span) -> meta::Span { + let filename = meta::convert_filename(&rspan.filename); let file_id = match &filename { FileName::NotReal(_) => { // For now we forbid not real filenames @@ -180,8 +342,8 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { FileName::Virtual(_) | FileName::Local(_) => self.register_file(filename), }; - let beg = meta::convert_loc(beg); - let end = meta::convert_loc(end); + let beg = meta::convert_loc(rspan.lo); + let end = meta::convert_loc(rspan.hi); // Put together meta::Span { file_id, beg, end } @@ -190,12 +352,12 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Compute meta data from a Rust source scope pub fn translate_meta_from_source_info( &mut self, - source_scopes: &IndexVec>, - source_info: SourceInfo, + source_scopes: &hax::IndexVec, + source_info: &hax::SourceInfo, ) -> Meta { // Translate the span let mut scope_data = source_scopes.get(source_info.scope).unwrap(); - let span = self.translate_span(scope_data.span); + let span = self.translate_span(scope_data.span.clone()); // Lookup the top-most inlined parent scope. if scope_data.inlined_parent_scope.is_some() { @@ -204,7 +366,7 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { scope_data = source_scopes.get(parent_scope).unwrap(); } - let parent_span = self.translate_span(scope_data.span); + let parent_span = self.translate_span(scope_data.span.clone()); Meta { span: parent_span, @@ -218,8 +380,9 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { } } - pub(crate) fn translate_meta_from_rspan(&mut self, rspan: rustc_span::Span) -> Meta { - // Translate teh span + // TODO: rename + pub(crate) fn translate_meta_from_rspan(&mut self, rspan: hax::Span) -> Meta { + // Translate the span let span = self.translate_span(rspan); Meta { @@ -228,26 +391,26 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { } } - pub(crate) fn id_is_opaque(&self, id: DefId) -> bool { - let name = crate::names_utils::item_def_id_to_name(self.tcx, id); + pub(crate) fn id_is_opaque(&mut self, id: DefId) -> bool { + let name = self.item_def_id_to_name(id); self.crate_info.is_opaque_decl(&name) } - pub(crate) fn id_is_transparent(&self, id: DefId) -> bool { + pub(crate) fn id_is_transparent(&mut self, id: DefId) -> bool { !self.id_is_opaque(id) } - pub(crate) fn push_id(&mut self, _rust_id: DefId, id: AnyRustId, trans_id: AnyTransId) { + pub(crate) fn push_id(&mut self, _rust_id: DefId, id: OrdRustId, trans_id: AnyTransId) { // Add the id to the stack of declarations to translate self.stack.insert(id); self.all_ids.insert(trans_id); } - pub(crate) fn register_type_decl_id(&mut self, id: DefId) -> ty::TypeDeclId::Id { - match self.type_id_map.get(id) { + pub(crate) fn register_type_decl_id(&mut self, id: DefId) -> TypeDeclId::Id { + match self.type_id_map.get(&id) { Option::Some(id) => id, Option::None => { - let rid = AnyRustId::Type(id); + let rid = OrdRustId::Type(id); let trans_id = self.type_id_map.insert(id); self.push_id(id, rid, AnyTransId::Type(trans_id)); trans_id @@ -255,15 +418,20 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { } } - pub(crate) fn translate_type_decl_id(&mut self, id: DefId) -> ty::TypeDeclId::Id { + pub(crate) fn translate_type_decl_id(&mut self, id: DefId) -> TypeDeclId::Id { self.register_type_decl_id(id) } + // TODO: factor out all the "register_..." functions pub(crate) fn register_fun_decl_id(&mut self, id: DefId) -> ast::FunDeclId::Id { - match self.fun_id_map.get(id) { + match self.fun_id_map.get(&id) { Option::Some(id) => id, Option::None => { - let rid = AnyRustId::Fun(id); + let rid = if self.tcx.is_const_fn_raw(id) { + OrdRustId::ConstFun(id) + } else { + OrdRustId::Fun(id) + }; let trans_id = self.fun_id_map.insert(id); self.push_id(id, rid, AnyTransId::Fun(trans_id)); trans_id @@ -271,15 +439,74 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { } } + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn register_trait_decl_id(&mut self, id: DefId) -> Option { + use crate::assumed; + if assumed::IGNORE_BUILTIN_MARKER_TRAITS { + let name = self.item_def_id_to_name(id); + if assumed::is_marker_trait(&name) { + return None; + } + } + + match self.trait_decl_id_map.get(&id) { + Option::Some(id) => Some(id), + Option::None => { + let rid = OrdRustId::TraitDecl(id); + let trans_id = self.trait_decl_id_map.insert(id); + self.push_id(id, rid, AnyTransId::TraitDecl(trans_id)); + Some(trans_id) + } + } + } + + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn register_trait_impl_id( + &mut self, + rust_id: DefId, + ) -> Option { + // Check if we need to filter + { + // Retrieve the id of the implemented trait decl + let id = self.tcx.trait_id_of_impl(rust_id).unwrap(); + let _ = self.register_trait_decl_id(id)?; + } + + match self.trait_impl_id_map.get(&rust_id) { + Option::Some(id) => Some(id), + Option::None => { + let rid = OrdRustId::TraitImpl(rust_id); + let trans_id = self.trait_impl_id_map.insert(rust_id); + self.trait_impl_id_to_def_id.insert(trans_id, rust_id); + self.push_id(rust_id, rid, AnyTransId::TraitImpl(trans_id)); + Some(trans_id) + } + } + } + pub(crate) fn translate_fun_decl_id(&mut self, id: DefId) -> ast::FunDeclId::Id { self.register_fun_decl_id(id) } - pub(crate) fn register_global_decl_id(&mut self, id: DefId) -> ty::GlobalDeclId::Id { - match self.global_id_map.get(id) { + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn translate_trait_decl_id(&mut self, id: DefId) -> Option { + self.register_trait_decl_id(id) + } + + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn translate_trait_impl_id(&mut self, id: DefId) -> Option { + self.register_trait_impl_id(id) + } + + pub(crate) fn register_global_decl_id(&mut self, id: DefId) -> GlobalDeclId::Id { + match self.global_id_map.get(&id) { Option::Some(id) => id, Option::None => { - let rid = AnyRustId::Global(id); + let rid = OrdRustId::Global(id); let trans_id = self.global_id_map.insert(id); self.push_id(id, rid, AnyTransId::Global(trans_id)); trans_id @@ -295,108 +522,135 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// Create a new `ExecContext`. pub(crate) fn new(def_id: DefId, t_ctx: &'ctx mut TransCtx<'tcx, 'ctx1>) -> Self { + let hax_state = t_ctx.make_hax_state_with_id(def_id); + let mut trait_clauses_counter = TraitClauseId::Generator::new(); + let trait_instance_id_gen = Box::new(move || { + let id = trait_clauses_counter.fresh_id(); + TraitInstanceId::Clause(id) + }); BodyTransCtx { def_id, t_ctx, - regions_counter: ty::RegionVarId::Generator::new(), - region_vars: ty::RegionVarId::Vector::new(), - region_vars_map: im::OrdMap::new(), - type_vars_counter: ty::TypeVarId::Generator::new(), - type_vars: ty::TypeVarId::Vector::new(), - type_vars_map: im::OrdMap::new(), - vars_counter: v::VarId::Generator::new(), - vars: v::VarId::Vector::new(), - vars_map: im::OrdMap::new(), - const_generic_counter: ty::ConstGenericVarId::Generator::new(), - const_generic_vars: ty::ConstGenericVarId::Vector::new(), - const_generic_vars_map: im::OrdMap::new(), - blocks_counter: ast::BlockId::Generator::new(), + hax_state, + region_vars: RegionId::Vector::new(), + region_vars_map: RegionId::MapGenerator::new(), + type_vars: TypeVarId::Vector::new(), + type_vars_map: TypeVarId::MapGenerator::new(), + vars: VarId::Vector::new(), + vars_map: VarId::MapGenerator::new(), + const_generic_vars: ConstGenericVarId::Vector::new(), + const_generic_vars_map: ConstGenericVarId::MapGenerator::new(), + trait_instance_id_gen, + trait_clauses: OrdMap::new(), + registering_trait_clauses: false, + regions_outlive: Vec::new(), + types_outlive: Vec::new(), + trait_type_constraints: Vec::new(), blocks: im::OrdMap::new(), - blocks_map: im::OrdMap::new(), + blocks_map: ast::BlockId::MapGenerator::new(), + bound_region_vars: im::Vector::new(), } } + pub fn continue_on_failure(&self) -> bool { + self.t_ctx.continue_on_failure() + } + + pub fn span_err(&mut self, span: rustc_span::Span, msg: &str) { + self.t_ctx.span_err(span, msg) + } + pub(crate) fn translate_meta_from_rid(&mut self, def_id: DefId) -> Meta { self.t_ctx.translate_meta_from_rid(def_id) } - pub(crate) fn translate_meta_from_rspan(&mut self, rspan: rustc_span::Span) -> Meta { + pub(crate) fn translate_meta_from_rspan(&mut self, rspan: hax::Span) -> Meta { self.t_ctx.translate_meta_from_rspan(rspan) } - pub(crate) fn get_local(&self, local: &mir::Local) -> Option { - self.vars_map.get(&local.as_u32()).copied() + pub(crate) fn get_local(&self, local: &hax::Local) -> Option { + use rustc_index::Idx; + self.vars_map.get(&local.index()) } - pub(crate) fn get_block_id_from_rid(&self, rid: BasicBlock) -> Option { - self.blocks_map.get(&rid).copied() + #[allow(dead_code)] + pub(crate) fn get_block_id_from_rid(&self, rid: hax::BasicBlock) -> Option { + self.blocks_map.get(&rid) } - pub(crate) fn get_var_from_id(&self, var_id: v::VarId::Id) -> Option<&ast::Var> { + pub(crate) fn get_var_from_id(&self, var_id: VarId::Id) -> Option<&ast::Var> { self.vars.get(var_id) } - pub(crate) fn register_type_decl_id(&mut self, id: DefId) -> ty::TypeDeclId::Id { - self.t_ctx.register_type_decl_id(id) - } - - pub(crate) fn translate_type_decl_id(&mut self, id: DefId) -> ty::TypeDeclId::Id { + pub(crate) fn translate_type_decl_id(&mut self, id: DefId) -> TypeDeclId::Id { self.t_ctx.translate_type_decl_id(id) } - pub(crate) fn register_fun_decl_id(&mut self, id: DefId) -> ast::FunDeclId::Id { - self.t_ctx.register_fun_decl_id(id) - } - pub(crate) fn translate_fun_decl_id(&mut self, id: DefId) -> ast::FunDeclId::Id { self.t_ctx.translate_fun_decl_id(id) } - pub(crate) fn register_global_decl_id(&mut self, id: DefId) -> ty::GlobalDeclId::Id { - self.t_ctx.register_global_decl_id(id) - } - pub(crate) fn translate_global_decl_id(&mut self, id: DefId) -> ast::GlobalDeclId::Id { self.t_ctx.translate_global_decl_id(id) } - pub(crate) fn get_region_from_rust( - &self, - r: rustc_middle::ty::RegionKind<'tcx>, - ) -> Option { - self.region_vars_map.get(&r).copied() + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn translate_trait_decl_id(&mut self, id: DefId) -> Option { + self.t_ctx.translate_trait_decl_id(id) } - pub(crate) fn push_region( - &mut self, - r: rustc_middle::ty::RegionKind<'tcx>, - name: Option, - ) -> ty::RegionVarId::Id { + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn translate_trait_impl_id(&mut self, id: DefId) -> Option { + self.t_ctx.translate_trait_impl_id(id) + } + + pub(crate) fn push_region(&mut self, r: hax::Region, name: Option) -> RegionId::Id { use crate::id_vector::ToUsize; - let rid = self.regions_counter.fresh_id(); + let rid = self.region_vars_map.insert(r); assert!(rid.to_usize() == self.region_vars.len()); - let var = ty::RegionVar { index: rid, name }; + let var = RegionVar { index: rid, name }; self.region_vars.insert(rid, var); - self.region_vars_map.insert(r, rid); rid } - pub(crate) fn push_type_var(&mut self, rindex: u32, name: String) -> ty::TypeVarId::Id { + /// Push a group of bound regions + pub(crate) fn push_bound_regions_group(&mut self, names: Vec>) { + use crate::id_vector::ToUsize; + + // Register the variables + let var_ids: im::Vector = names + .into_iter() + .map(|name| { + // Note that we don't insert a binding in the region_vars_map + let rid = self.region_vars_map.fresh_id(); + assert!(rid.to_usize() == self.region_vars.len()); + let var = RegionVar { index: rid, name }; + self.region_vars.insert(rid, var); + rid + }) + .collect(); + + // Push the group + self.bound_region_vars.push_front(var_ids); + } + + pub(crate) fn push_type_var(&mut self, rindex: u32, name: String) -> TypeVarId::Id { use crate::id_vector::ToUsize; - let var_id = self.type_vars_counter.fresh_id(); + let var_id = self.type_vars_map.insert(rindex); assert!(var_id.to_usize() == self.type_vars.len()); - let var = ty::TypeVar { + let var = TypeVar { index: var_id, name, }; self.type_vars.insert(var_id, var); - self.type_vars_map.insert(rindex, var_id); var_id } - pub(crate) fn push_var(&mut self, rid: u32, ty: ty::ETy, name: Option) { + pub(crate) fn push_var(&mut self, rid: usize, ty: Ty, name: Option) { use crate::id_vector::ToUsize; - let var_id = self.vars_counter.fresh_id(); + let var_id = self.vars_map.insert(rid); assert!(var_id.to_usize() == self.vars.len()); let var = ast::Var { index: var_id, @@ -404,194 +658,768 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { ty, }; self.vars.insert(var_id, var); - self.vars_map.insert(rid, var_id); } pub(crate) fn push_const_generic_var(&mut self, rid: u32, ty: LiteralTy, name: String) { use crate::id_vector::ToUsize; - let var_id = self.const_generic_counter.fresh_id(); - assert!(var_id.to_usize() == self.vars.len()); - let var = ty::ConstGenericVar { + let var_id = self.const_generic_vars_map.insert(rid); + assert!(var_id.to_usize() == self.const_generic_vars.len()); + let var = ConstGenericVar { index: var_id, name, ty, }; self.const_generic_vars.insert(var_id, var); - self.const_generic_vars_map.insert(rid, var_id); } - pub(crate) fn fresh_block_id(&mut self, rid: BasicBlock) -> ast::BlockId::Id { - let block_id = self.blocks_counter.fresh_id(); - self.blocks_map.insert(rid, block_id); - block_id + pub(crate) fn fresh_block_id(&mut self, rid: hax::BasicBlock) -> ast::BlockId::Id { + self.blocks_map.insert(rid) } pub(crate) fn push_block(&mut self, id: ast::BlockId::Id, block: ast::BlockData) { self.blocks.insert(id, block); } - pub(crate) fn get_type_defs(&self) -> &ty::TypeDecls { - &self.t_ctx.type_defs + pub(crate) fn get_generics(&self) -> GenericParams { + GenericParams { + regions: self.region_vars.clone(), + types: self.type_vars.clone(), + const_generics: self.const_generic_vars.clone(), + trait_clauses: self.get_local_trait_clauses(), + } + } + + /// Retrieve the *local* trait clauses available in the current environment + /// (we filter the parent of those clauses, etc.). + pub(crate) fn get_local_trait_clauses(&self) -> TraitClauseId::Vector { + let clauses: TraitClauseId::Vector = self + .trait_clauses + .iter() + .filter_map(|(_, x)| x.to_local_trait_clause()) + .collect(); + // Sanity check + assert!(clauses.iter_indexed_values().all(|(i, c)| c.clause_id == i)); + clauses + } + + pub(crate) fn get_parent_trait_clauses(&self) -> TraitClauseId::Vector { + let clauses: TraitClauseId::Vector = self + .trait_clauses + .iter() + .filter_map(|(_, x)| { + x.to_trait_clause_with_id(&|id| match id { + TraitInstanceId::ParentClause(box TraitInstanceId::SelfId, _, id) => Some(*id), + _ => None, + }) + }) + .collect(); + // Sanity check + assert!(clauses.iter_indexed_values().all(|(i, c)| c.clause_id == i)); + clauses + } + + pub(crate) fn get_predicates(&self) -> Predicates { + Predicates { + regions_outlive: self.regions_outlive.clone(), + types_outlive: self.types_outlive.clone(), + trait_type_constraints: self.trait_type_constraints.clone(), + } + } + + /// We use this when exploring the clauses of a predicate, to introduce + /// its parent clauses, etc. in the context. We temporarily replace the + /// trait instance id generator so that the continuation registers the + /// + pub(crate) fn with_local_trait_clauses( + &mut self, + new_trait_instance_id_gen: Box TraitInstanceId>, + f: &mut dyn FnMut(&mut Self) -> T, + ) -> T { + use std::mem::replace; + + // Save the trait instance id generator + let old_trait_instance_id_gen = + replace(&mut self.trait_instance_id_gen, new_trait_instance_id_gen); + + // Apply the continuation + let out = f(self); + + // Restore + self.trait_instance_id_gen = old_trait_instance_id_gen; + + // Return + out + } + + /// Set [registering_trait_clauses] to [true], call the continuation, and + /// reset it to [false] + pub(crate) fn while_registering_trait_clauses( + &mut self, + f: &mut dyn FnMut(&mut Self) -> T, + ) -> T { + assert!(!self.registering_trait_clauses); + self.registering_trait_clauses = true; + let out = f(self); + self.registering_trait_clauses = false; + out + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: TypeDeclId::Id) -> String { + match self.type_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: GlobalDeclId::Id) -> String { + match self.global_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: RegionId::Id) -> String { + id.to_pretty_string() + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: TypeVarId::Id) -> String { + id.to_pretty_string() } } -impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { - fn format_object(&self, id: ty::TypeDeclId::Id) -> String { - self.type_defs.format_object(id) +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: ConstGenericVarId::Id) -> String { + id.to_pretty_string() } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: ty::TypeVarId::Id) -> String { +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: ast::FunDeclId::Id) -> String { + match self.fun_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: ast::TraitClauseId::Id) -> String { + id.to_pretty_string() + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: ast::TraitDeclId::Id) -> String { + match self.trait_decls.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: ast::TraitImplId::Id) -> String { + match self.trait_impls.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +/// For enum values: `List::Cons` +impl<'tcx, 'ctx> Formatter<(TypeDeclId::Id, VariantId::Id)> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, id: (TypeDeclId::Id, VariantId::Id)) -> String { + let (def_id, variant_id) = id; + // The definition may not be available yet, especially if we print-debug + // while translating the crate + match self.type_defs.get(def_id) { + Option::None => format!( + "{}::{}", + def_id.to_pretty_string(), + variant_id.to_pretty_string() + ), + Option::Some(def) => { + let variants = def.kind.as_enum(); + let mut name = def.name.fmt_with_ctx(self); + let variant_name = &variants.get(variant_id).unwrap().name; + name.push_str("::"); + name.push_str(variant_name); + name + } + } + } +} + +/// For struct/enum values: retrieve a field name +impl<'tcx, 'ctx> Formatter<(TypeDeclId::Id, Option, FieldId::Id)> + for TransCtx<'tcx, 'ctx> +{ + fn format_object(&self, id: (TypeDeclId::Id, Option, FieldId::Id)) -> String { + let (def_id, opt_variant_id, field_id) = id; + // The definition may not be available yet, especially if we print-debug + // while translating the crate + match self.type_defs.get(def_id) { + Option::None => match opt_variant_id { + Option::None => format!( + "{}::{}", + def_id.to_pretty_string(), + field_id.to_pretty_string() + ), + Option::Some(variant_id) => format!( + "{}::{}::{}", + def_id.to_pretty_string(), + variant_id.to_pretty_string(), + field_id.to_pretty_string() + ), + }, + Option::Some(gen_def) => match (&gen_def.kind, opt_variant_id) { + (TypeDeclKind::Enum(variants), Some(variant_id)) => { + let field = variants + .get(variant_id) + .unwrap() + .fields + .get(field_id) + .unwrap(); + match &field.name { + Option::Some(name) => name.clone(), + Option::None => field_id.to_string(), + } + } + (TypeDeclKind::Struct(fields), None) => { + let field = fields.get(field_id).unwrap(); + match &field.name { + Option::Some(name) => name.clone(), + Option::None => field_id.to_string(), + } + } + _ => unreachable!(), + }, + } + } +} + +impl<'tcx, 'ctx> Formatter for TransCtx<'tcx, 'ctx> { + fn format_object(&self, v: VarId::Id) -> String { + v.to_pretty_string() + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: TypeVarId::Id) -> String { let v = self.type_vars.get(id).unwrap(); v.to_string() } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: v::VarId::Id) -> String { +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ConstGenericVarId::Id) -> String { + let v = self.const_generic_vars.get(id).unwrap(); + v.to_string() + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: VarId::Id) -> String { let v = self.vars.get(id).unwrap(); v.to_string() } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: ty::RegionVarId::Id) -> String { +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: RegionId::Id) -> String { let v = self.region_vars.get(id).unwrap(); v.to_string() } } -impl<'tcx, 'ctx, 'ctx1> Formatter<&ty::Region> +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: TypeDeclId::Id) -> String { + match self.t_ctx.type_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: GlobalDeclId::Id) -> String { + match self.t_ctx.global_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&Ty> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, ty: &Ty) -> String { + ty.fmt_with_ctx(self) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitClauseId::Id) -> String { + id.to_pretty_string() + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitDeclId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitImplId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::FunDeclId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: TypeVarId::Id) -> String { + match self.generics.types.get(id) { + None => id.to_pretty_string(), + Some(v) => v.to_string(), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ConstGenericVarId::Id) -> String { + match self.generics.const_generics.get(id) { + None => id.to_pretty_string(), + Some(v) => v.to_string(), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: VarId::Id) -> String { + match self.vars.get(id) { + None => id.to_pretty_string(), + Some(v) => v.to_string(), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: RegionId::Id) -> String { + match self.generics.regions.get(id) { + None => id.to_pretty_string(), + Some(v) => v.to_string(), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: TypeDeclId::Id) -> String { + match self.t_ctx.type_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: GlobalDeclId::Id) -> String { + match self.t_ctx.global_defs.get(id) { + None => id.to_pretty_string(), + Some(d) => d.name.fmt_with_ctx(self), + } + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&Ty> for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, ty: &Ty) -> String { + ty.fmt_with_ctx(self) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitClauseId::Id) -> String { + id.to_pretty_string() + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitDeclId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::TraitImplId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, id: ast::FunDeclId::Id) -> String { + self.t_ctx.format_object(id) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&TypeDecl> for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &TypeDecl) -> String { + def.fmt_with_ctx(self) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<(TypeDeclId::Id, VariantId::Id)> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, r: &ty::Region) -> String { - r.fmt_with_ctx(self) + fn format_object(&self, id: (TypeDeclId::Id, VariantId::Id)) -> String { + self.t_ctx.format_object(id) } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: ty::ConstGenericVarId::Id) -> String { - let v = self.const_generic_vars.get(id).unwrap(); - v.to_string() +impl<'tcx, 'ctx, 'ctx1> Formatter<(TypeDeclId::Id, Option, FieldId::Id)> + for BodyTransCtx<'tcx, 'ctx, 'ctx1> +{ + fn format_object(&self, id: (TypeDeclId::Id, Option, FieldId::Id)) -> String { + self.t_ctx.format_object(id) } } -impl<'tcx, 'ctx, 'ctx1> Formatter<&ty::ErasedRegion> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, _: &ty::ErasedRegion) -> String { - "'_".to_owned() +impl<'tcx, 'ctx, 'ctx1> Formatter<(TypeDeclId::Id, VariantId::Id)> + for BodyFormatCtx<'tcx, 'ctx, 'ctx1> +{ + fn format_object(&self, id: (TypeDeclId::Id, VariantId::Id)) -> String { + self.t_ctx.format_object(id) } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: ty::TypeDeclId::Id) -> String { - self.t_ctx.type_defs.format_object(id) +impl<'tcx, 'ctx, 'ctx1> Formatter<(TypeDeclId::Id, Option, FieldId::Id)> + for BodyFormatCtx<'tcx, 'ctx, 'ctx1> +{ + fn format_object(&self, id: (TypeDeclId::Id, Option, FieldId::Id)) -> String { + self.t_ctx.format_object(id) } } -impl<'tcx, 'ctx, 'ctx1> Formatter for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, id: ty::GlobalDeclId::Id) -> String { - self.t_ctx.global_defs.format_object(id) +impl<'tcx, 'ctx> Formatter<&TypeDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &TypeDecl) -> String { + // Create a body format context + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &def.generics, + vars: &VarId::Vector::new(), + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) } } -impl<'tcx, 'ctx, 'ctx1> Formatter<&ty::Ty>> - for BodyTransCtx<'tcx, 'ctx, 'ctx1> +impl<'tcx, 'ctx, 'ctx1> Formatter<&llbc_ast::Statement> for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, x: &llbc_ast::Statement) -> String { + x.fmt_with_ctx(TAB_INCR, self) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&ullbc_ast::BlockId::Vector> + for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, ty: &ty::Ty>) -> String { - ty.fmt_with_ctx(self) + fn format_object(&self, x: &ullbc_ast::BlockId::Vector) -> String { + ullbc_ast::fmt_body_blocks_with_ctx(x, TAB_INCR, self) } } -impl<'tcx, 'ctx, 'ctx1> Formatter<&ty::Ty> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, ty: &ty::Ty) -> String { - ty.fmt_with_ctx(self) +impl<'tcx, 'ctx, 'ctx1> Formatter<&TypeDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &TypeDecl) -> String { + self.t_ctx.format_object(def) } } -/// Auxiliary definition used to format definitions. -pub(crate) struct TypeDeclFormatter<'a> { - pub type_defs: &'a ty::TypeDecls, - pub global_defs: &'a ast::GlobalDecls, - /// The region parameters of the definition we are printing (needed to - /// correctly pretty print region var ids) - pub region_params: &'a ty::RegionVarId::Vector, - /// The type parameters of the definition we are printing (needed to - /// correctly pretty print type var ids) - pub type_params: &'a ty::TypeVarId::Vector, - /// The const generic parameters of the definition we are printing (needed to - /// correctly pretty print type var ids) - pub const_generic_params: &'a ty::ConstGenericVarId::Vector, +impl<'tcx, 'ctx> Formatter<&ullbc_ast::ExprBody> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, body: &ullbc_ast::ExprBody) -> String { + // Create a body format context + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &GenericParams::empty(), + vars: &body.locals, + trait_clauses: &TraitClauseId::Vector::new(), + }; + body.fmt_with_ctx(TAB_INCR, &formatter) + } } -impl<'a> Formatter for TypeDeclFormatter<'a> { - fn format_object(&self, id: ty::RegionVarId::Id) -> String { - // Lookup the region parameter - let v = self.region_params.get(id).unwrap(); - // Format - v.to_string() +impl<'tcx, 'ctx, 'ctx1> Formatter<&ullbc_ast::ExprBody> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, body: &ullbc_ast::ExprBody) -> String { + self.t_ctx.format_object(body) } } -impl<'a> Formatter for TypeDeclFormatter<'a> { - fn format_object(&self, id: ty::ConstGenericVarId::Id) -> String { - // Lookup the region parameter - let v = self.const_generic_params.get(id).unwrap(); - // Format - v.to_string() +impl<'tcx, 'ctx> Formatter<&llbc_ast::ExprBody> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, body: &llbc_ast::ExprBody) -> String { + // Create a body format context + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &GenericParams::empty(), + vars: &body.locals, + trait_clauses: &TraitClauseId::Vector::new(), + }; + body.fmt_with_ctx(TAB_INCR, &formatter) } } -impl<'a> Formatter for TypeDeclFormatter<'a> { - fn format_object(&self, id: ty::TypeVarId::Id) -> String { - // Lookup the type parameter - let v = self.type_params.get(id).unwrap(); - // Format - v.to_string() +impl<'tcx, 'ctx, 'ctx1> Formatter<&llbc_ast::ExprBody> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, body: &llbc_ast::ExprBody) -> String { + self.t_ctx.format_object(body) + } +} + +impl<'tcx, 'ctx> Formatter<&ullbc_ast::GlobalDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &ullbc_ast::GlobalDecl) -> String { + // Create a body format context + let empty_vars = VarId::Vector::new(); + let vars = match &def.body { + None => &empty_vars, + Some(body) => &body.locals, + }; + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &GenericParams::empty(), + vars, + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&ullbc_ast::GlobalDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &ullbc_ast::GlobalDecl) -> String { + self.t_ctx.format_object(def) + } +} + +impl<'tcx, 'ctx> Formatter<&llbc_ast::GlobalDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &llbc_ast::GlobalDecl) -> String { + // Create a body format context + let empty_vars = VarId::Vector::new(); + let vars = match &def.body { + None => &empty_vars, + Some(body) => &body.locals, + }; + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &GenericParams::empty(), + vars, + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&llbc_ast::GlobalDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &llbc_ast::GlobalDecl) -> String { + self.t_ctx.format_object(def) } } -impl<'a> Formatter<&ty::Region> for TypeDeclFormatter<'a> { - fn format_object(&self, r: &ty::Region) -> String { - r.fmt_with_ctx(self) +impl<'tcx, 'ctx> Formatter<&FunSig> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, sig: &FunSig) -> String { + // Create a body format context + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &sig.generics, + vars: &VarId::Vector::new(), + trait_clauses: &TraitClauseId::Vector::new(), + }; + sig.fmt_with_ctx(&formatter) } } -impl<'a> Formatter<&ty::ErasedRegion> for TypeDeclFormatter<'a> { - fn format_object(&self, _: &ty::ErasedRegion) -> String { - "".to_owned() +impl<'tcx, 'ctx, 'ctx1> Formatter<&FunSig> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, sig: &FunSig) -> String { + self.t_ctx.format_object(sig) } } -impl<'a> Formatter<&ty::TypeDecl> for TypeDeclFormatter<'a> { - fn format_object(&self, def: &ty::TypeDecl) -> String { +impl<'tcx, 'ctx> Formatter<&Ty> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, x: &Ty) -> String { + x.fmt_with_ctx(self) + } +} + +impl<'tcx, 'ctx> Formatter<&ullbc_ast::FunDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &ullbc_ast::FunDecl) -> String { + // Create a body format context + let empty_vars = VarId::Vector::new(); + let vars = match &def.body { + None => &empty_vars, + Some(body) => &body.locals, + }; + let sig = &def.signature; + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &sig.generics, + vars, + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&ullbc_ast::FunDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &ullbc_ast::FunDecl) -> String { + self.t_ctx.format_object(def) + } +} + +impl<'tcx, 'ctx> Formatter<&llbc_ast::FunDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &llbc_ast::FunDecl) -> String { + // Create a body format context + let empty_vars = VarId::Vector::new(); + let vars = match &def.body { + None => &empty_vars, + Some(body) => &body.locals, + }; + let sig = &def.signature; + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &sig.generics, + vars, + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&llbc_ast::FunDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &llbc_ast::FunDecl) -> String { + self.t_ctx.format_object(def) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&gast::TraitDecl> for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &gast::TraitDecl) -> String { def.fmt_with_ctx(self) } } -impl<'a> Formatter for TypeDeclFormatter<'a> { - fn format_object(&self, id: ty::TypeDeclId::Id) -> String { - self.type_defs.format_object(id) +impl<'tcx, 'ctx> Formatter<&gast::TraitDecl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &gast::TraitDecl) -> String { + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &def.generics, + vars: &VarId::Vector::new(), + trait_clauses: &TraitClauseId::Vector::new(), + }; + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&gast::TraitDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &gast::TraitDecl) -> String { + self.t_ctx.format_object(def) } } -impl<'a> Formatter for TypeDeclFormatter<'a> { - fn format_object(&self, id: ty::GlobalDeclId::Id) -> String { - self.global_defs.format_object(id) +impl<'tcx, 'ctx, 'ctx1> Formatter<&gast::TraitImpl> for BodyFormatCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &gast::TraitImpl) -> String { + def.fmt_with_ctx(self) } } -impl<'tcx, 'ctx, 'ctx1> Formatter<&ty::TypeDecl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { - fn format_object(&self, def: &ty::TypeDecl) -> String { - // Create a type def formatter (which will take care of the - // type parameters) - let formatter = TypeDeclFormatter { - type_defs: &self.t_ctx.type_defs, - global_defs: &self.t_ctx.global_defs, - region_params: &def.region_params, - type_params: &def.type_params, - const_generic_params: &def.const_generic_params, +impl<'tcx, 'ctx> Formatter<&gast::TraitImpl> for TransCtx<'tcx, 'ctx> { + fn format_object(&self, def: &gast::TraitImpl) -> String { + let formatter = BodyFormatCtx { + t_ctx: self, + generics: &def.generics, + vars: &VarId::Vector::new(), + trait_clauses: &TraitClauseId::Vector::new(), }; - formatter.format_object(def) + def.fmt_with_ctx(&formatter) + } +} + +impl<'tcx, 'ctx, 'ctx1> Formatter<&gast::TraitImpl> for BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn format_object(&self, def: &gast::TraitImpl) -> String { + self.t_ctx.format_object(def) + } +} + +impl<'tcx, 'ctx> fmt::Display for TransCtx<'tcx, 'ctx> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // We do simple: types, globals, traits, functions + for (_, d) in &self.type_defs { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.global_defs { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.trait_decls { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.trait_impls { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.fun_defs { + writeln!(f, "{}\n", self.format_object(d))? + } + + fmt::Result::Ok(()) + } +} + +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { + fn fmt_with_llbc_defs( + &self, + f: &mut fmt::Formatter, + llbc_globals: &llbc_ast::GlobalDecls, + llbc_funs: &llbc_ast::FunDecls, + ) -> fmt::Result { + // We do simple: types, globals, traits, functions + for (_, d) in &self.type_defs { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in llbc_globals { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.trait_decls { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in &self.trait_impls { + writeln!(f, "{}\n", self.format_object(d))? + } + + for (_, d) in llbc_funs { + writeln!(f, "{}\n", self.format_object(d))? + } + + fmt::Result::Ok(()) + } +} + +pub(crate) struct LlbcTransCtx<'a, 'tcx, 'ctx> { + pub ctx: &'a TransCtx<'tcx, 'ctx>, + pub llbc_globals: &'a llbc_ast::GlobalDecls, + pub llbc_funs: &'a llbc_ast::FunDecls, +} + +impl<'a, 'tcx, 'ctx> fmt::Display for LlbcTransCtx<'a, 'tcx, 'ctx> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.ctx + .fmt_with_llbc_defs(f, self.llbc_globals, self.llbc_funs) } } diff --git a/charon/src/translate_functions_to_ullbc.rs b/charon/src/translate_functions_to_ullbc.rs index ff32dc57..1bea3f53 100644 --- a/charon/src/translate_functions_to_ullbc.rs +++ b/charon/src/translate_functions_to_ullbc.rs @@ -3,410 +3,67 @@ //! us to handle, and easier to maintain - rustc's representation can evolve //! independently. -#![allow(dead_code)] use crate::assumed; use crate::common::*; -use crate::expressions as e; +use crate::expressions::*; use crate::formatter::Formatter; -use crate::generics; use crate::get_mir::{boxes_are_desugared, get_mir_for_def_id_and_level}; -use crate::id_vector; -use crate::names::global_def_id_to_name; -use crate::names::{function_def_id_to_name, type_def_id_to_name}; -use crate::regions_hierarchy::RegionGroups; use crate::translate_ctx::*; use crate::translate_types; -use crate::types as ty; -use crate::types::{FieldId, VariantId}; -use crate::ullbc_ast as ast; -use crate::values as v; -use crate::values::{Literal, ScalarValue}; -use core::convert::*; -use log::warn; -use rustc_abi::FieldIdx; +use crate::types::*; +use crate::ullbc_ast::*; +use crate::values::*; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_middle::mir; -use rustc_middle::mir::{ - BasicBlock, Body, Operand, Place, PlaceElem, Statement, StatementKind, Terminator, - TerminatorKind, START_BLOCK, -}; -use rustc_middle::ty as mir_ty; -use rustc_middle::ty::adjustment::PointerCast; -use rustc_middle::ty::{TyCtxt, TyKind}; -use rustc_span::Span; -use std::iter::FromIterator; -use translate_types::{translate_erased_region, translate_region_name}; - -fn translate_variant_id(id: rustc_target::abi::VariantIdx) -> VariantId::Id { - VariantId::Id::new(id.as_usize()) +use rustc_middle::mir::START_BLOCK; +use rustc_middle::ty; +use translate_types::translate_bound_region_kind_name; + +pub(crate) struct SubstFunId { + pub func: FnPtr, + pub args: Option>, +} + +pub(crate) enum SubstFunIdOrPanic { + Panic, + Fun(SubstFunId), } -fn translate_field_id(id: FieldIdx) -> FieldId::Id { - FieldId::Id::new(id.as_usize()) +fn translate_variant_id(id: hax::VariantIdx) -> VariantId::Id { + VariantId::Id::new(id) +} + +fn translate_field_id(id: hax::FieldIdx) -> FieldId::Id { + use rustc_index::Idx; + FieldId::Id::new(id.index()) } /// Translate a `BorrowKind` -fn translate_borrow_kind(borrow_kind: mir::BorrowKind) -> e::BorrowKind { +fn translate_borrow_kind(borrow_kind: hax::BorrowKind) -> BorrowKind { match borrow_kind { - mir::BorrowKind::Shared => e::BorrowKind::Shared, - mir::BorrowKind::Mut { + hax::BorrowKind::Shared => BorrowKind::Shared, + hax::BorrowKind::Mut { allow_two_phase_borrow, } => { if allow_two_phase_borrow { - e::BorrowKind::TwoPhaseMut + BorrowKind::TwoPhaseMut } else { - e::BorrowKind::Mut + BorrowKind::Mut } } - mir::BorrowKind::Unique => { + hax::BorrowKind::Unique => { unimplemented!(); } - mir::BorrowKind::Shallow => e::BorrowKind::Shallow, - } -} - -fn translate_binaryop_kind(binop: mir::BinOp) -> e::BinOp { - use mir::BinOp; - match binop { - BinOp::BitXor => e::BinOp::BitXor, - BinOp::BitAnd => e::BinOp::BitAnd, - BinOp::BitOr => e::BinOp::BitOr, - BinOp::Eq => e::BinOp::Eq, - BinOp::Lt => e::BinOp::Lt, - BinOp::Le => e::BinOp::Le, - BinOp::Ne => e::BinOp::Ne, - BinOp::Ge => e::BinOp::Ge, - BinOp::Gt => e::BinOp::Gt, - BinOp::Div => e::BinOp::Div, - BinOp::Rem => e::BinOp::Rem, - BinOp::Add => e::BinOp::Add, - BinOp::Sub => e::BinOp::Sub, - BinOp::Mul => e::BinOp::Mul, - BinOp::Shl => e::BinOp::Shl, - BinOp::Shr => e::BinOp::Shr, - _ => { - unreachable!(); - } + hax::BorrowKind::Shallow => BorrowKind::Shallow, } } -fn translate_unaryop_kind(binop: mir::UnOp) -> e::UnOp { - use mir::UnOp; +fn translate_unaryop_kind(binop: hax::UnOp) -> UnOp { match binop { - UnOp::Not => e::UnOp::Not, - UnOp::Neg => e::UnOp::Neg, - } -} - -/// Build an uninterpreted constant from a MIR constant identifier. -fn rid_as_unevaluated_constant<'tcx>(id: DefId) -> rustc_middle::mir::UnevaluatedConst<'tcx> { - let p = mir_ty::List::empty(); - rustc_middle::mir::UnevaluatedConst::new(id, p) -} - -/// Return the `DefId` of the function referenced by an operand, with the -/// parameters substitution. -/// The `Operand` comes from a `TerminatorKind::Call`. -/// Only supports calls to top-level functions (which are considered as constants -/// by rustc); doesn't support closures for now. -fn get_function_from_operand<'tcx>( - func: &Operand<'tcx>, -) -> (DefId, &'tcx rustc_middle::ty::subst::InternalSubsts<'tcx>) { - trace!("func: {:?}", func); - - use std::ops::Deref; - // Match on the func operand: it should be a constant as we don't support - // closures for now. - match func { - mir::Operand::Constant(c) => { - trace!("Operand::Constant: {:?}", c); - let c = c.deref(); - match &c.literal { - mir::ConstantKind::Ty(c) => { - // The type of the constant should be a FnDef, allowing - // us to retrieve the function's identifier and instantiation. - let c_ty = c.ty(); - assert!(c_ty.is_fn()); - match c_ty.kind() { - mir_ty::TyKind::FnDef(def_id, subst) => (*def_id, subst), - _ => { - unreachable!(); - } - } - } - mir::ConstantKind::Val(cv, c_ty) => { - trace!("cv: {:?}, ty: {:?}", cv, c_ty); - // Same as for the `Ty` case above - assert!(c_ty.is_fn()); - match c_ty.kind() { - mir_ty::TyKind::FnDef(def_id, subst) => (*def_id, subst), - _ => { - unreachable!(); - } - } - } - mir::ConstantKind::Unevaluated(_, _) => { - unimplemented!(); - } - } - } - mir::Operand::Move(_place) | mir::Operand::Copy(_place) => { - unimplemented!(); - } - } -} - -/// A function definition can be top-level, or can be defined in an `impl` -/// block. In this case, we might want to retrieve the type for which the -/// impl block was defined. This function returns this type's def id if -/// the function def id given as input was defined in an impl block, and -/// returns `None` otherwise. -/// -/// For instance, when translating `bar` below: -/// ```text -/// impl Foo { -/// fn bar(...) -> ... { ... } -/// } -/// ``` -/// we might want to know that `bar` is actually defined in one of `Foo`'s impl -/// blocks (and retrieve `Foo`'s identifier). -/// -/// TODO: this might gives us the same as TyCtxt::generics_of -fn get_impl_parent_type_def_id(tcx: TyCtxt, def_id: DefId) -> Option { - // Retrieve the definition def id - let def_key = tcx.def_key(def_id); - - // Reconstruct the parent def id: it's the same as the function's def id, - // at the exception of the index. - let parent_def_id = DefId { - index: def_key.parent.unwrap(), - ..def_id - }; - - // Retrieve the parent's key - let parent_def_key = tcx.def_key(parent_def_id); - - // Match on the parent key - let parent = match parent_def_key.disambiguated_data.data { - rustc_hir::definitions::DefPathData::Impl => { - // Parent is an impl block! Retrieve the type definition (it - // seems that `type_of` is the only way of getting it) - let parent_type = tcx.type_of(parent_def_id).subst_identity(); - - // The parent type should be ADT - match parent_type.kind() { - rustc_middle::ty::TyKind::Adt(adt_def, _) => Some(adt_def.did()), - _ => { - unreachable!(); - } - } - } - _ => { - // Not an impl block - None - } - }; - - // TODO: checking - assert!(parent == tcx.generics_of(def_id).parent); - - parent -} - -/// Translate a call to a function considered primitive and which is not: -/// panic, begin_panic, box_free (those have a *very* special treatment). -fn translate_primitive_function_call( - tcx: TyCtxt<'_>, - def_id: DefId, - region_args: Vec, - type_args: Vec, - const_generic_args: Vec, - args: Vec, - dest: e::Place, - target: ast::BlockId::Id, -) -> Result { - trace!("- def_id: {:?}", def_id,); - - // Translate the function name - let name = function_def_id_to_name(tcx, def_id); - trace!("name: {}", name); - - // We assume the function has primitive support, and look up - // its primitive identifier - let aid = assumed::get_fun_id_from_name(&name, &type_args).unwrap(); - - // Translate the function call - // Note that some functions are actually traits (deref, index, etc.): - // we assume that they are called only on a limited set of types - // (ex.: box, vec...). - // For those trait functions, we need a custom treatment to retrieve - // and check the type information. - // For instance, derefencing boxes generates MIR of the following form: - // ``` - // _2 = as Deref>::deref(move _3) - // ``` - // We have to retrieve the type `Box` and check that it is of the - // form `Box` (and we generate `box_deref`). - match aid { - ast::AssumedFunId::Replace - | ast::AssumedFunId::BoxNew - | ast::AssumedFunId::VecNew - | ast::AssumedFunId::VecPush - | ast::AssumedFunId::VecInsert - | ast::AssumedFunId::VecLen - | ast::AssumedFunId::SliceLen => { - let call = ast::Call { - func: ast::FunId::Assumed(aid), - region_args, - type_args, - const_generic_args, - args, - dest, - }; - Ok(ast::RawTerminator::Call { call, target }) - } - ast::AssumedFunId::BoxDeref | ast::AssumedFunId::BoxDerefMut => translate_box_deref( - aid, - region_args, - type_args, - const_generic_args, - args, - dest, - target, - ), - ast::AssumedFunId::VecIndex | ast::AssumedFunId::VecIndexMut => translate_vec_index( - aid, - region_args, - type_args, - const_generic_args, - args, - dest, - target, - ), - ast::AssumedFunId::ArraySubsliceShared - | ast::AssumedFunId::ArraySubsliceMut - | ast::AssumedFunId::SliceSubsliceShared - | ast::AssumedFunId::SliceSubsliceMut => { - // Take a subslice from an array/slice. - // Note that this isn't any different from a regular function call. Ideally, - // we'd have a generic assumed function mechanism. - assert!(type_args.len() == 1); - assert!(args.len() == 2); - assert!(const_generic_args.is_empty()); - // We need to unwrap the type to retrieve the `T` inside the `Slice` - // or the `Array` - let (_, _, type_args, const_generic_args) = type_args[0].clone().to_adt(); - assert!(type_args.len() == 1); - assert!(const_generic_args.len() <= 1); - - let call = ast::Call { - func: ast::FunId::Assumed(aid), - region_args, - type_args, - const_generic_args, - args, - dest, - }; - - Ok(ast::RawTerminator::Call { call, target }) - } - ast::AssumedFunId::BoxFree => { - // Special case handled elsewhere - unreachable!(); - } - ast::AssumedFunId::ArrayIndexShared - | ast::AssumedFunId::ArrayIndexMut - | ast::AssumedFunId::ArrayToSliceShared - | ast::AssumedFunId::ArrayToSliceMut - | ast::AssumedFunId::SliceIndexShared - | ast::AssumedFunId::SliceIndexMut => { - // Those cases are introduced later, in micro-passes, by desugaring - // projections (for ArrayIndex and ArrayIndexMut for instnace) and= - // operations (for ArrayToSlice for instance) to function calls. - unreachable!() - } - } -} - -/// Translate `std::Deref::deref` or `std::DerefMut::deref_mut` applied -/// on boxes. We need a custom function because it is a trait. -fn translate_box_deref( - aid: ast::AssumedFunId, - region_args: Vec, - type_args: Vec, - const_generic_args: Vec, - args: Vec, - dest: e::Place, - target: ast::BlockId::Id, -) -> Result { - // Check the arguments - assert!(region_args.is_empty()); - assert!(type_args.len() == 1); - assert!(args.len() == 1); - - // For now we only support deref on boxes - // Retrieve the boxed value - let arg_ty = type_args.get(0).unwrap(); // should be `Box<...>` - let boxed_ty = arg_ty.as_box(); - if boxed_ty.is_none() { - panic!( - "Deref/DerefMut trait applied with parameter {:?} while it is only supported on Box values", - arg_ty - ); + hax::UnOp::Not => UnOp::Not, + hax::UnOp::Neg => UnOp::Neg, } - let boxed_ty = boxed_ty.unwrap(); - let type_args = vec![boxed_ty.clone()]; - - let call = ast::Call { - func: ast::FunId::Assumed(aid), - region_args, - type_args, - const_generic_args, - args, - dest, - }; - Ok(ast::RawTerminator::Call { call, target }) -} - -/// Translate `core::ops::index::{Index,IndexMut}::{index,index_mut}` -/// applied on `Vec`. We need a custom function because it is a trait. -fn translate_vec_index( - aid: ast::AssumedFunId, - region_args: Vec, - type_args: Vec, - const_generic_args: Vec, - args: Vec, - dest: e::Place, - target: ast::BlockId::Id, -) -> Result { - // Check the arguments - assert!(region_args.is_empty()); - assert!(type_args.len() == 1); - assert!(args.len() == 2); - - // For now we only support index on vectors - // Retrieve the boxed value - let arg_ty = type_args.get(0).unwrap(); // should be `Vec<...>` - let arg_ty = match arg_ty.as_vec() { - Option::Some(ty) => ty, - Option::None => { - panic!( - "Index/IndexMut trait applied with parameter {:?} while it is only supported on Vec values", - arg_ty - ); - } - }; - - let type_args = vec![arg_ty.clone()]; - let call = ast::Call { - func: ast::FunId::Assumed(aid), - region_args, - type_args, - const_generic_args, - args, - dest, - }; - Ok(ast::RawTerminator::Call { call, target }) } /// Small utility @@ -424,39 +81,163 @@ pub(crate) fn check_impl_item(impl_item: &rustc_hir::Impl<'_>) { assert!(impl_item.defaultness == Defaultness::Final); // Note sure what this is about assert!(impl_item.constness == Constness::NotConst); - assert!(impl_item.of_trait.is_none()); // We don't support traits for now } -impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { - /// Translate a function's local variables by adding them in the environment. - fn translate_body_locals(&mut self, body: &Body<'tcx>) -> Result<()> { - // First, retrieve the debug info - we want to retrieve the names - // of the variables (which otherwise are just referenced with indices). - // This is mostly to generate a clean and readable translation later on. - // It seems the only way of linking the locals to the debug info is - // through the spans. - let mut span_to_var_name: im::OrdMap = im::OrdMap::new(); - for info in &body.var_debug_info { - span_to_var_name.insert(info.source_info.span, info.name.to_ident_string()); +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { + fn translate_binaryop_kind( + &mut self, + span: rustc_span::Span, + binop: hax::BinOp, + ) -> Result { + match binop { + hax::BinOp::BitXor => Ok(BinOp::BitXor), + hax::BinOp::BitAnd => Ok(BinOp::BitAnd), + hax::BinOp::BitOr => Ok(BinOp::BitOr), + hax::BinOp::Eq => Ok(BinOp::Eq), + hax::BinOp::Lt => Ok(BinOp::Lt), + hax::BinOp::Le => Ok(BinOp::Le), + hax::BinOp::Ne => Ok(BinOp::Ne), + hax::BinOp::Ge => Ok(BinOp::Ge), + hax::BinOp::Gt => Ok(BinOp::Gt), + hax::BinOp::Div => Ok(BinOp::Div), + hax::BinOp::Rem => Ok(BinOp::Rem), + hax::BinOp::Add => Ok(BinOp::Add), + hax::BinOp::Sub => Ok(BinOp::Sub), + hax::BinOp::Mul => Ok(BinOp::Mul), + hax::BinOp::Shl => Ok(BinOp::Shl), + hax::BinOp::Shr => Ok(BinOp::Shr), + hax::BinOp::Offset => { + error_or_panic!(self, span, "Unsupported binary operation: offset") + } } + } + pub(crate) fn get_fun_kind(&mut self, rust_id: DefId) -> FunKind { + trace!("rust_id: {:?}", rust_id); + let tcx = self.tcx; + if let Some(assoc) = tcx.opt_associated_item(rust_id) { + match assoc.container { + ty::AssocItemContainer::ImplContainer => { + // This method is defined in an impl block. + // It can be a regular function in an impl block or a trait + // method implementation. + // + // Ex.: + // ==== + // ``` + // impl List { + // fn new() -> Self { ... } <- implementation + // } + // + // impl Foo for Bar { + // fn baz(...) { ... } // <- implementation of a trait method + // } + // ``` + + // Check if there is a trait item (if yes, it is a trait method + // implementation, if no it is a regular function). + // Remark: this trait item is the id of the item associated + // in the trait. For instance: + // ``` + // trait Foo { + // fn bar(); // Id: Foo_bar + // } + // ``` + // + // impl Foo for List { + // fn bar() { ... } // trait_item_def_id: Some(Foo_bar) + // } + match assoc.trait_item_def_id { + None => FunKind::Regular, + Some(trait_method_id) => { + let trait_id = tcx.trait_of_item(trait_method_id).unwrap(); + let trait_id = self.translate_trait_decl_id(trait_id); + // The trait id should be Some(...): trait markers (that we + // may eliminate) don't have methods. + let trait_id = trait_id.unwrap(); + + // Retrieve the id of the impl block + let impl_id = self + .translate_trait_impl_id(tcx.predicates_of(rust_id).parent.unwrap()) + .unwrap(); + + let method_name = self.translate_trait_item_name(trait_method_id); + + // Check if the current function implements a provided method. + // We do so by retrieving the def id of the method which is + // implemented, and checking its kind. + let provided = match self.get_fun_kind(trait_method_id) { + FunKind::TraitMethodDecl(..) => false, + FunKind::TraitMethodProvided(..) => true, + FunKind::Regular | FunKind::TraitMethodImpl { .. } => { + unreachable!() + } + }; + + FunKind::TraitMethodImpl { + impl_id, + trait_id, + method_name, + provided, + } + } + } + } + ty::AssocItemContainer::TraitContainer => { + // This method is the *declaration* of a trait method + // Ex.: + // ==== + // ``` + // trait Foo { + // fn baz(...); // <- declaration of a trait method + // } + // ``` + + // Yet, this could be a default method implementation, in which + // case there is a body: we need to check that. + + // In order to check if this is a provided method, we check + // the defaultness (i.e., whether the method has a default value): + let is_provided = tcx.impl_defaultness(rust_id).has_value(); + + // Compute additional information + let method_name = self.translate_trait_item_name(rust_id); + let trait_id = tcx.trait_of_item(rust_id).unwrap(); + let trait_id = self.translate_trait_decl_id(trait_id); + // The trait id should be Some(...): trait markers (that we + // may eliminate) don't have methods. + let trait_id = trait_id.unwrap(); + + if is_provided { + FunKind::TraitMethodProvided(trait_id, method_name) + } else { + FunKind::TraitMethodDecl(trait_id, method_name) + } + } + } + } else { + FunKind::Regular + } + } +} + +impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { + /// Translate a function's local variables by adding them in the environment. + fn translate_body_locals(&mut self, body: &hax::MirBody<()>) -> Result<(), Error> { // Translate the parameters - for (index, var) in body.local_decls.iter_enumerated() { - trace!( - "Translating local of index {} and type {:?}", - index.as_usize(), - var.ty - ); + for (index, var) in body.local_decls.raw.iter().enumerate() { + trace!("Translating local of index {} and type {:?}", index, var.ty); // Find the name of the variable - let span = var.source_info.span; - let name: Option = span_to_var_name.get(&span).cloned(); + let name: Option = var.name.clone(); // Translate the type - let ty = self.translate_ety(&var.ty)?; + let erase_regions = true; + let span = var.source_info.span.rust_span; + let ty = self.translate_ty(span, erase_regions, &var.ty)?; // Add the variable to the environment - self.push_var(index.as_u32(), ty, name); + self.push_var(index, ty, name); } Ok(()) @@ -466,23 +247,26 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// /// The local variables should already have been translated and inserted in /// the context. - fn translate_transparent_expression_body(&mut self, body: &Body<'tcx>) -> Result<()> { + fn translate_transparent_expression_body( + &mut self, + body: &hax::MirBody<()>, + ) -> Result<(), Error> { trace!(); - let id = self.translate_basic_block(body, START_BLOCK)?; - assert!(id == ast::START_BLOCK_ID); + let id = self.translate_basic_block(body, rustc_index::Idx::new(START_BLOCK.as_usize()))?; + assert!(id == START_BLOCK_ID); Ok(()) } fn translate_basic_block( &mut self, - body: &Body<'tcx>, - block_id: BasicBlock, - ) -> Result { + body: &hax::MirBody<()>, + block_id: hax::BasicBlock, + ) -> Result { // Check if the block has already been translated if let Some(id) = self.blocks_map.get(&block_id) { - return Ok(*id); + return Ok(id); } let nid = self.fresh_block_id(block_id); @@ -502,11 +286,11 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } // Translate the terminator - let terminator = block.terminator(); + let terminator = block.terminator.as_ref().unwrap(); let terminator = self.translate_terminator(body, terminator)?; // Insert the block in the translated blocks - let block = ast::BlockData { + let block = BlockData { statements, terminator, }; @@ -517,257 +301,178 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } /// Translate a place and return its type - fn translate_place_with_type(&mut self, place: &Place<'tcx>) -> (e::Place, ty::ETy) { - let var_id = self.get_local(&place.local).unwrap(); - let var = self.get_var_from_id(var_id).unwrap(); - let (projection, ty) = self.translate_projection(var.ty.clone(), place.projection); - - (e::Place { var_id, projection }, ty) + fn translate_place_with_type( + &mut self, + span: rustc_span::Span, + place: &hax::Place, + ) -> Result<(Place, Ty), Error> { + let erase_regions = true; + let ty = self.translate_ty(span, erase_regions, &place.ty)?; + let (var_id, projection) = self.translate_projection(span, place)?; + Ok((Place { var_id, projection }, ty)) } /// Translate a place - fn translate_place(&mut self, place: &Place<'tcx>) -> e::Place { - self.translate_place_with_type(place).0 + fn translate_place( + &mut self, + span: rustc_span::Span, + place: &hax::Place, + ) -> Result { + Ok(self.translate_place_with_type(span, place)?.0) } - /// Translate a projection - /// - /// We use the variable type to disambiguate between different kinds of - /// projections. For instance, rust uses `Deref` both to dereference mutable/shared - /// references and to move values from inside a box. On our side, we distinguish - /// the two kinds of dereferences. - /// - /// We return the translated projection, and its type. - /// - /// - `mir_level`: used for sanity checks + /// Translate a place - TODO: rename + /// TODO: Hax represents places in a different manner than MIR. We should + /// update our representation of places to match the Hax representation. fn translate_projection( &mut self, - var_ty: ty::ETy, - rprojection: &rustc_middle::ty::List>, - ) -> (e::Projection, ty::ETy) { - trace!("- projection: {:?}\n- var_ty: {:?}", rprojection, var_ty); - - // We need to track the type of the value we look at, while exploring the path. - // This is important to disambiguate, for instance, dereferencement operations. - let mut path_type = var_ty; - // When projection an ADT's field, we need to remember what variant it - // should be in case of an enumeration (such information is introduced - // by Downcast projections *before* the field projection). - let mut downcast_id: Option = None; - - let mut projection = e::Projection::new(); - for pelem in rprojection { - trace!("- pelem: {:?}\n- path_type: {:?}", pelem, path_type); - match pelem { - mir::ProjectionElem::Deref => { - downcast_id = None; - // We need to disambiguate the two kinds of dereferences - use std::ops::Deref; - match path_type { - ty::Ty::Ref(_, ty, _) => { - path_type = ty.deref().clone(); - projection.push(e::ProjectionElem::Deref); - } - ty::Ty::Adt(ty::TypeId::Assumed(ty::AssumedTy::Box), regions, tys, cgs) => { - // This case only happens in some MIR levels - assert!(!boxes_are_desugared(self.t_ctx.mir_level)); - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - path_type = tys[0].clone(); - projection.push(e::ProjectionElem::DerefBox); - } - ty::Ty::RawPtr(ty, _) => { - path_type = ty.deref().clone(); - projection.push(e::ProjectionElem::DerefRawPtr); - } - _ => { - unreachable!("\n- pelem: {:?}\n- path_type: {:?}", pelem, path_type); + span: rustc_span::Span, + place: &hax::Place, + ) -> Result<(VarId::Id, Projection), Error> { + let erase_regions = true; + match &place.kind { + hax::PlaceKind::Local(local) => { + let var_id = self.get_local(local).unwrap(); + Ok((var_id, Vec::new())) + } + hax::PlaceKind::Projection { place, kind } => { + let (var_id, mut projection) = self.translate_projection(span, place)?; + // Compute the type of the value *before* projection - we use this + // to disambiguate + let current_ty = self.translate_ty(span, erase_regions, &place.ty)?; + match kind { + hax::ProjectionElem::Deref => { + // We use the type to disambiguate + match current_ty { + Ty::Ref(_, _, _) => { + projection.push(ProjectionElem::Deref); + } + Ty::Adt(TypeId::Assumed(AssumedTy::Box), generics) => { + // This case only happens in some MIR levels + assert!(!boxes_are_desugared(self.t_ctx.mir_level)); + assert!(generics.regions.is_empty()); + assert!(generics.types.len() == 1); + assert!(generics.const_generics.is_empty()); + projection.push(ProjectionElem::DerefBox); + } + Ty::RawPtr(_, _) => { + projection.push(ProjectionElem::DerefRawPtr); + } + _ => { + unreachable!( + "\n- place.kind: {:?}\n- current_ty: {:?}", + kind, current_ty + ); + } } } - } - mir::ProjectionElem::Field(field, field_ty) => { - let field_id = translate_field_id(field); - // Update the path type and generate the proj kind at the - // same time. - let proj_elem = match path_type { - ty::Ty::Adt(ty::TypeId::Adt(type_id), _regions, _tys, _cgs) => { - path_type = self.translate_ety(&field_ty).unwrap(); - - let proj_kind = e::FieldProjKind::Adt(type_id, downcast_id); - e::ProjectionElem::Field(proj_kind, field_id) - } - ty::Ty::Adt(ty::TypeId::Tuple, regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(downcast_id.is_none()); - assert!(cgs.is_empty()); - path_type = tys.get(field.as_usize()).unwrap().clone(); - let proj_kind = e::FieldProjKind::Tuple(tys.len()); - e::ProjectionElem::Field(proj_kind, field_id) - } - ty::Ty::Adt( - ty::TypeId::Assumed(ty::AssumedTy::Option), - regions, - tys, - cgs, - ) => { - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - assert!(downcast_id.is_some()); - assert!(field_id == ty::FieldId::ZERO); - - path_type = tys.get(0).unwrap().clone(); - let variant_id = downcast_id.unwrap(); - assert!(variant_id == assumed::OPTION_SOME_VARIANT_ID); - let proj_kind = e::FieldProjKind::Option(variant_id); - e::ProjectionElem::Field(proj_kind, field_id) - } - ty::Ty::Adt(ty::TypeId::Assumed(aty), regions, tys, cgs) - if aty == ty::AssumedTy::Box - || aty == ty::AssumedTy::PtrUnique - || aty == ty::AssumedTy::PtrNonNull => - { - // The Box case only happens in some MIR levels. - // We group the following cases here: - // ``` - // (x:Box).0: std::ptr::Unique - // (x:std::ptr::Unique).0: std::ptr::NonNull - // (x:std::ptr::NonNull).0: *const T // raw pointer - // ``` - assert!(!aty.is_box() || boxes_are_desugared(self.t_ctx.mir_level)); - - // Some more sanity checks - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - assert!(downcast_id.is_none()); - assert!(field_id == ty::FieldId::ZERO); - - // Retrieve the type given by `T` above - let type_param = tys.get(0).unwrap().clone(); - - // Find the new field type - let elem; - match aty { - ty::AssumedTy::Box => { - elem = e::ProjectionElem::DerefBox; - path_type = ty::Ty::Adt( - ty::TypeId::Assumed(ty::AssumedTy::PtrUnique), - vec![], - vec![type_param], - vec![], - ) - } - ty::AssumedTy::PtrUnique => { - elem = e::ProjectionElem::DerefPtrUnique; - path_type = ty::Ty::Adt( - ty::TypeId::Assumed(ty::AssumedTy::PtrNonNull), - vec![], - vec![type_param], - vec![], - ) - } - ty::AssumedTy::PtrNonNull => { - elem = e::ProjectionElem::DerefPtrNonNull; - path_type = - ty::Ty::RawPtr(Box::new(type_param), ty::RefKind::Shared) + hax::ProjectionElem::Field(field_kind) => { + use hax::ProjectionElemFieldKind::*; + let proj_elem = match field_kind { + Tuple(id) => { + let (_, generics) = current_ty.as_adt(); + let field_id = translate_field_id(*id); + let proj_kind = FieldProjKind::Tuple(generics.types.len()); + ProjectionElem::Field(proj_kind, field_id) + } + Adt { + typ: _, + variant, + index, + } => { + let field_id = translate_field_id(*index); + let variant_id = variant.map(translate_variant_id); + match current_ty { + Ty::Adt(TypeId::Adt(type_id), ..) => { + let proj_kind = FieldProjKind::Adt(type_id, variant_id); + ProjectionElem::Field(proj_kind, field_id) + } + Ty::Adt(TypeId::Tuple, generics) => { + assert!(generics.regions.is_empty()); + assert!(variant.is_none()); + assert!(generics.const_generics.is_empty()); + let proj_kind = FieldProjKind::Tuple(generics.types.len()); + + ProjectionElem::Field(proj_kind, field_id) + } + Ty::Adt(TypeId::Assumed(AssumedTy::Box), generics) => { + assert!(!boxes_are_desugared(self.t_ctx.mir_level)); + + // Some more sanity checks + assert!(generics.regions.is_empty()); + assert!(generics.types.len() == 1); + assert!(generics.const_generics.is_empty()); + assert!(variant_id.is_none()); + assert!(field_id == FieldId::ZERO); + + ProjectionElem::DerefBox + } + _ => { + error_or_panic!(self, span, "Unexpected field projection"); + } } - _ => unreachable!(), - }; - - elem - } - _ => { - trace!("Path type: {:?}", path_type); - unreachable!(); - } - }; - projection.push(proj_elem); - downcast_id = None; - } - mir::ProjectionElem::Index(local) => match &path_type { - ty::Ty::Adt( - ty::TypeId::Assumed(ty::AssumedTy::Array | ty::AssumedTy::Slice), - _, - tys, - _, - ) => { - assert!(tys.len() == 1); - - let v = self.get_local(&local).unwrap(); - projection.push(e::ProjectionElem::Index(v, path_type.clone())); - - path_type = tys[0].clone(); + } + }; + projection.push(proj_elem); } - _ => { - unreachable!("ProjectionElem::Index, path_type:\n{:?}", path_type) + hax::ProjectionElem::Index(local) => { + let local = self.get_local(local).unwrap(); + projection.push(ProjectionElem::Index(local, current_ty)); } - }, - mir::ProjectionElem::ConstantIndex { - offset: _, - min_length: _, - from_end: _, - } => { - // This doesn't seem to occur in MIR built - unimplemented!(); - } - mir::ProjectionElem::Subslice { - from: _, - to: _, - from_end: _, - } => { - // This doesn't seem to occur in MIR built - unimplemented!(); - } - mir::ProjectionElem::OpaqueCast(_) => { - unimplemented!(); - } - mir::ProjectionElem::Downcast(_, variant_id) => { - // Downcast an enum to a specific variant. - // For example, the left value of: - // `((_0 as Right).0: T2) = move _1;` - // Note that on the above example, the downcast preceeds the - // field projection. - let vid = translate_variant_id(variant_id); - // Don't update the variable type - // Remember the new downcast - downcast_id = Some(vid); - // We don't translate downcasts: the information is merged with - // field projections - } + hax::ProjectionElem::Downcast(..) => { + // We view it as a nop (the information from the + // downcast has been propagated to the other + // projection elements by Hax) + } + hax::ProjectionElem::ConstantIndex { .. } + | hax::ProjectionElem::Subslice { .. } => { + // Those don't seem to occur in MIR built + error_or_panic!(self, span, "Unexpected ProjectionElem::Subslice"); + } + hax::ProjectionElem::OpaqueCast => { + // Don't know what that is + error_or_panic!(self, span, "Unexpected ProjectionElem::OpaqueCast"); + } + }; + + // Return + Ok((var_id, projection)) } } - - (projection, path_type) } /// Translate an operand with its type fn translate_operand_with_type( &mut self, - operand: &mir::Operand<'tcx>, - ) -> (e::Operand, ty::ETy) { + span: rustc_span::Span, + operand: &hax::Operand, + ) -> Result<(Operand, Ty), Error> { trace!(); match operand { - Operand::Copy(place) => { - let (p, ty) = self.translate_place_with_type(place); - (e::Operand::Copy(p), ty) + hax::Operand::Copy(place) => { + let (p, ty) = self.translate_place_with_type(span, place)?; + Ok((Operand::Copy(p), ty)) } - Operand::Move(place) => { - let (p, ty) = self.translate_place_with_type(place); - (e::Operand::Move(p), ty) + hax::Operand::Move(place) => { + let (p, ty) = self.translate_place_with_type(span, place)?; + Ok((Operand::Move(p), ty)) } - Operand::Constant(constant) => { - let (ty, constant) = self.translate_operand_constant(constant); - (e::Operand::Const(ty.clone(), constant), ty) + hax::Operand::Constant(constant) => { + let constant = self.translate_constant_to_constant_expr(span, constant)?; + let ty = constant.ty.clone(); + Ok((Operand::Const(constant), ty)) } } } /// Translate an operand - fn translate_operand(&mut self, operand: &mir::Operand<'tcx>) -> e::Operand { + fn translate_operand( + &mut self, + span: rustc_span::Span, + operand: &hax::Operand, + ) -> Result { trace!(); - self.translate_operand_with_type(operand).0 + Ok(self.translate_operand_with_type(span, operand)?.0) } /// Translate an operand which should be `move b.0` where `b` is a box (such @@ -782,31 +487,19 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// reveals implementation details). fn translate_move_box_first_projector_operand( &mut self, - operand: &mir::Operand<'tcx>, - ) -> e::Operand { + span: rustc_span::Span, + operand: &hax::Operand, + ) -> Result { trace!(); match operand { - Operand::Move(place) => { - let var_id = self.get_local(&place.local).unwrap(); + hax::Operand::Move(place) => { + let place = self.translate_place(span, place)?; // Sanity check - let var = self.get_var_from_id(var_id).unwrap(); + let var = self.get_var_from_id(place.var_id).unwrap(); assert!(var.ty.is_box()); - assert!(place.projection.len() == 1); - let proj_elem = place.projection.get(0).unwrap(); - match proj_elem { - mir::ProjectionElem::Field(field, _) => { - assert!(field.as_usize() == 0); - } - _ => { - unreachable!(); - } - } - e::Operand::Move(e::Place { - var_id, - projection: e::Projection::new(), - }) + Ok(Operand::Move(place)) } _ => { unreachable!(); @@ -815,85 +508,81 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } /// Translate an rvalue - fn translate_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>) -> e::Rvalue { + fn translate_rvalue( + &mut self, + span: rustc_span::Span, + rvalue: &hax::Rvalue, + ) -> Result { use std::ops::Deref; + let erase_regions = true; match rvalue { - mir::Rvalue::Use(operand) => e::Rvalue::Use(self.translate_operand(operand)), - mir::Rvalue::CopyForDeref(place) => { + hax::Rvalue::Use(operand) => Ok(Rvalue::Use(self.translate_operand(span, operand)?)), + hax::Rvalue::CopyForDeref(place) => { // According to the documentation, it seems to be an optimisation // for drop elaboration. We treat it as a regular copy. - let place = self.translate_place(place); - e::Rvalue::Use(e::Operand::Copy(place)) + let place = self.translate_place(span, place)?; + Ok(Rvalue::Use(Operand::Copy(place))) } - mir::Rvalue::Repeat(operand, cnst) => { - let c = self.translate_const_kind_as_const_generic(*cnst); - // We are effectively desugaring the repeat in Charon, turning it into an array literal - // where the operand is repeated `cnst` times. - // TODO: allow having other kinds of const generics, and desugar later to a function call - let cv = *c.as_value().as_scalar().as_usize(); - let (operand, t) = self.translate_operand_with_type(operand); - let mut operands = Vec::with_capacity(cv as usize); - for _ in 0..cv { - operands.push(operand.clone()); - } - // We *have* to desugar here; we don't have enough context (the destination place, the - // lifetime variable) to translate this into a built-in function call. This is why we - // don't have a ArrayRepeat AssumedFunId. - e::Rvalue::Aggregate(e::AggregateKind::Array(t, c), operands) + hax::Rvalue::Repeat(operand, cnst) => { + let c = self.translate_constant_expr_to_const_generic(span, cnst)?; + let (operand, t) = self.translate_operand_with_type(span, operand)?; + // Remark: we could desugar this into a function call later. + Ok(Rvalue::Repeat(operand, t, c)) } - mir::Rvalue::Ref(_region, borrow_kind, place) => { - let place = self.translate_place(place); + hax::Rvalue::Ref(_region, borrow_kind, place) => { + let place = self.translate_place(span, place)?; let borrow_kind = translate_borrow_kind(*borrow_kind); - e::Rvalue::Ref(place, borrow_kind) + Ok(Rvalue::Ref(place, borrow_kind)) } - mir::Rvalue::ThreadLocalRef(_) => { - unreachable!(); + hax::Rvalue::ThreadLocalRef(_) => { + error_or_panic!(self, span, "Unsupported rvalue: thread local ref"); } - mir::Rvalue::AddressOf(_, _) => { - unreachable!(); + hax::Rvalue::AddressOf(_, _) => { + error_or_panic!(self, span, "Unsupported rvalue: address of"); } - mir::Rvalue::Len(place) => { - let (place, ty) = self.translate_place_with_type(place); + hax::Rvalue::Len(place) => { + let (place, ty) = self.translate_place_with_type(span, place)?; let cg = match &ty { - ty::Ty::Adt( - ty::TypeId::Assumed(aty @ (ty::AssumedTy::Array | ty::AssumedTy::Slice)), - _, - _, - cgs, + Ty::Adt( + TypeId::Assumed(aty @ (AssumedTy::Array | AssumedTy::Slice)), + generics, ) => { if aty.is_array() { - Option::Some(cgs[0].clone()) + Option::Some(generics.const_generics[0].clone()) } else { Option::None } } _ => unreachable!(), }; - e::Rvalue::Len(place, ty, cg) + Ok(Rvalue::Len(place, ty, cg)) } - mir::Rvalue::Cast(cast_kind, operand, tgt_ty) => { + hax::Rvalue::Cast(cast_kind, operand, tgt_ty) => { trace!("Rvalue::Cast: {:?}", rvalue); // Put aside the pointer casts (which we don't support), I think // casts should only be from integers/booleans to integer/booleans. // Translate the target type - let tgt_ty = self.translate_ety(tgt_ty).unwrap(); + let tgt_ty = self.translate_ty(span, erase_regions, tgt_ty)?; // Translate the operand - let (op, src_ty) = self.translate_operand_with_type(operand); + let (op, src_ty) = self.translate_operand_with_type(span, operand)?; match (cast_kind, &src_ty, &tgt_ty) { - (rustc_middle::mir::CastKind::IntToInt, _, _) => { + (hax::CastKind::IntToInt, _, _) => { // We only support source and target types for integers let tgt_ty = *tgt_ty.as_literal().as_integer(); let src_ty = *src_ty.as_literal().as_integer(); - e::Rvalue::UnaryOp(e::UnOp::Cast(src_ty, tgt_ty), op) + Ok(Rvalue::UnaryOp( + UnOp::Cast(CastKind::Integer(src_ty, tgt_ty)), + op, + )) } ( - rustc_middle::mir::CastKind::Pointer(PointerCast::Unsize), - ty::Ty::Ref(_, t1, kind1), - ty::Ty::Ref(_, t2, kind2), + hax::CastKind::Pointer(hax::PointerCast::Unsize), + Ty::Ref(_, t1, kind1), + Ty::Ref(_, t2, kind2), ) => { // In MIR terminology, we go from &[T; l] to &[T] which means we // effectively "unsize" the type, as `l` no longer appears in the @@ -901,57 +590,84 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { // materializes into the fat pointer. match (&**t1, &**t2) { ( - ty::Ty::Adt(ty::TypeId::Assumed(ty::AssumedTy::Array), _, tys, cgs), - ty::Ty::Adt(ty::TypeId::Assumed(ty::AssumedTy::Slice), _, tys1, _), + Ty::Adt(TypeId::Assumed(AssumedTy::Array), generics), + Ty::Adt(TypeId::Assumed(AssumedTy::Slice), generics1), ) => { - assert!(tys.len() == 1 && cgs.len() == 1); - assert!(tys[0] == tys1[0]); + assert!( + generics.types.len() == 1 && generics.const_generics.len() == 1 + ); + assert!(generics.types[0] == generics1.types[0]); assert!(kind1 == kind2); - e::Rvalue::UnaryOp( - e::UnOp::ArrayToSlice(*kind1, tys[0].clone(), cgs[0].clone()), + Ok(Rvalue::UnaryOp( + UnOp::ArrayToSlice( + *kind1, + generics.types[0].clone(), + generics.const_generics[0].clone(), + ), op, - ) + )) } _ => { - panic!( - "Unsupported cast: {:?}, src={:?}, dst={:?}", - rvalue, src_ty, tgt_ty + error_or_panic!( + self, + span, + format!( + "Unsupported cast: {:?}, src={:?}, dst={:?}", + rvalue, src_ty, tgt_ty + ) ) } } } + ( + hax::CastKind::Pointer(hax::PointerCast::ClosureFnPointer(unsafety)), + src_ty @ Ty::Arrow(_, _), + tgt_ty @ Ty::Arrow(_, _), + ) => { + assert!(*unsafety == hax::Unsafety::Normal); + let src_ty = src_ty.clone(); + let tgt_ty = tgt_ty.clone(); + Ok(Rvalue::UnaryOp( + UnOp::Cast(CastKind::FnPtr(src_ty, tgt_ty)), + op, + )) + } _ => { - panic!( - "Unsupported cast: {:?}, src={:?}, dst={:?}", - rvalue, src_ty, tgt_ty + error_or_panic!( + self, + span, + format!( + "Unsupported cast:\n- rvalue: {:?}\n- src={:?}\n- dst={:?}", + rvalue, src_ty, tgt_ty + ) ) } } } - mir::Rvalue::BinaryOp(binop, operands) - | mir::Rvalue::CheckedBinaryOp(binop, operands) => { + hax::Rvalue::BinaryOp(binop, operands) + | hax::Rvalue::CheckedBinaryOp(binop, operands) => { // We merge checked and unchecked binary operations let (left, right) = operands.deref(); - e::Rvalue::BinaryOp( - translate_binaryop_kind(*binop), - self.translate_operand(left), - self.translate_operand(right), - ) + Ok(Rvalue::BinaryOp( + self.t_ctx.translate_binaryop_kind(span, *binop)?, + self.translate_operand(span, left)?, + self.translate_operand(span, right)?, + )) } - mir::Rvalue::NullaryOp(nullop, _ty) => { + hax::Rvalue::NullaryOp(nullop, _ty) => { trace!("NullOp: {:?}", nullop); // Nullary operations are very low-level and shouldn't be necessary // unless one needs to write unsafe code. - unreachable!(); + error_or_panic!(self, span, "Nullary operations are not supported"); } - mir::Rvalue::UnaryOp(unop, operand) => e::Rvalue::UnaryOp( + hax::Rvalue::UnaryOp(unop, operand) => Ok(Rvalue::UnaryOp( translate_unaryop_kind(*unop), - self.translate_operand(operand), - ), - mir::Rvalue::Discriminant(place) => { - e::Rvalue::Discriminant(self.translate_place(place)) + self.translate_operand(span, operand)?, + )), + hax::Rvalue::Discriminant(place) => { + Ok(Rvalue::Discriminant(self.translate_place(span, place)?)) } - mir::Rvalue::Aggregate(aggregate_kind, operands) => { + hax::Rvalue::Aggregate(aggregate_kind, operands) => { // It seems this instruction is not present in certain passes: // for example, it seems it is not used in optimized MIR, where // ADT initialization is split into several instructions, for @@ -964,31 +680,35 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { // p.x = x; // p.y = yv; // ``` - // - // Our semantics is designed to handle both cases (aggregated and - // non-aggregated initialization). // First translate the operands - let operands_t: Vec = operands + let operands_t: Vec = operands + .raw .iter() - .map(|op| self.translate_operand(op)) - .collect(); + .map(|op| self.translate_operand(span, op)) + .try_collect()?; match aggregate_kind.deref() { - mir::AggregateKind::Array(ty) => { - let t_ty = self.translate_ety(&ty).unwrap(); - let cg = ty::ConstGeneric::Value(Literal::Scalar(ScalarValue::Usize( + hax::AggregateKind::Array(ty) => { + let t_ty = self.translate_ty(span, erase_regions, ty)?; + let cg = ConstGeneric::Value(Literal::Scalar(ScalarValue::Usize( operands_t.len() as u64, ))); - e::Rvalue::Aggregate(e::AggregateKind::Array(t_ty, cg), operands_t) + Ok(Rvalue::Aggregate( + AggregateKind::Array(t_ty, cg), + operands_t, + )) } - mir::AggregateKind::Tuple => { - e::Rvalue::Aggregate(e::AggregateKind::Tuple, operands_t) - } - mir::AggregateKind::Adt( + hax::AggregateKind::Tuple => Ok(Rvalue::Aggregate( + AggregateKind::Adt(TypeId::Tuple, None, GenericArgs::empty()), + operands_t, + )), + hax::AggregateKind::Adt( adt_id, variant_idx, + kind, substs, + trait_refs, user_annotation, field_index, ) => { @@ -1000,93 +720,321 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { // the documentation seems outdated (it says the 4th parameter // is a field index, while it makes more sense for it to be // the 5th, and I don't know how I should use it anyway). - assert!(user_annotation.is_none()); - assert!(field_index.is_none()); + error_assert!(self, span, user_annotation.is_none()); + error_assert!(self, span, field_index.is_none()); // Translate the substitution - let (region_params, mut type_params, cg_params) = self - .translate_subst_generic_args_in_body(None, substs) - .unwrap(); - - if adt_id.is_local() { - assert!(!self.t_ctx.id_is_opaque(*adt_id)); - - // Local ADT: translate the id - let id_t = self.translate_type_decl_id(*adt_id); - - let kind = self.t_ctx.tcx.adt_def(adt_id).adt_kind(); - let variant_id = match kind { - rustc_middle::ty::AdtKind::Struct => Option::None, - rustc_middle::ty::AdtKind::Enum => { - let variant_id = translate_variant_id(*variant_idx); - Some(variant_id) - } - rustc_middle::ty::AdtKind::Union => { - unimplemented!(); - } - }; - - let akind = e::AggregateKind::Adt( - id_t, - variant_id, - region_params, - type_params, - cg_params, - ); - - e::Rvalue::Aggregate(akind, operands_t) - } else { - // External ADT. - // Can be `Option` - // TODO: treat all external ADTs in a consistant manner. - // For instance, we can access the variants of any external - // enumeration marked as `public`. - let name = type_def_id_to_name(self.t_ctx.tcx, *adt_id); - if name.equals_ref_name(&assumed::OPTION_NAME) { - // Sanity checks - assert!(region_params.is_empty()); - assert!(type_params.len() == 1); - - // Find the variant + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + None, + substs, + trait_refs, + )?; + + let type_id = self.translate_type_id(adt_id); + // Sanity check + matches!(&type_id, TypeId::Adt(_)); + + use hax::AdtKind; + let variant_id = match kind { + AdtKind::Struct => Option::None, + AdtKind::Enum => { let variant_id = translate_variant_id(*variant_idx); - if variant_id == assumed::OPTION_NONE_VARIANT_ID { - assert!(operands_t.is_empty()); - } else if variant_id == assumed::OPTION_SOME_VARIANT_ID { - assert!(operands_t.len() == 1); - } else { - unreachable!(); - } - - let akind = e::AggregateKind::Option( - variant_id, - type_params.pop().unwrap(), - ); - - e::Rvalue::Aggregate(akind, operands_t) - } else if name.equals_ref_name(&assumed::RANGE_NAME) { - // Sanity checks - assert!(region_params.is_empty()); - // Ranges are parametric over the type of indices - assert!(type_params.len() == 1); - e::Rvalue::Aggregate( - e::AggregateKind::Range(type_params.pop().unwrap()), - operands_t, - ) - } else { - panic!("Unsupported ADT: {}", name); + Some(variant_id) } - } + AdtKind::Union => { + error_or_panic!(self, span, "Union values are not supported"); + } + }; + + let akind = AggregateKind::Adt(type_id, variant_id, generics); + Ok(Rvalue::Aggregate(akind, operands_t)) } - mir::AggregateKind::Closure(_def_id, _subst) => { - unimplemented!(); + hax::AggregateKind::Closure(def_id, sig) => { + trace!("Closure:\n- def_id: {:?}\n- sig: {:?}", def_id, sig); + // We need to register the signature for def_id, so that + // we can later translate the closure the same way as top-level + // functions. + error_or_panic!(self, span, "Support for closures is not implemented yet") + /*let def_id = self.translate_fun_decl_id(def_id.rust_def_id.unwrap()); + let akind = AggregateKind::Closure(def_id); + Rvalue::Aggregate(akind, Vec::new()) */ } - mir::AggregateKind::Generator(_def_id, _subst, _movability) => { - unimplemented!(); + hax::AggregateKind::Generator(_def_id, _subst, _movability) => { + error_or_panic!(self, span, "Generators are not supported"); } } } - mir::Rvalue::ShallowInitBox(_, _) => { - unimplemented!(); + hax::Rvalue::ShallowInitBox(_, _) => { + error_or_panic!(self, span, "Unsupported rvalue: shallow init box"); + } + } + } + + /// Auxiliary function to translate function calls and references to functions. + /// Translate a function id applied with some substitutions and some optional + /// arguments. + /// + /// We use a special function because the function might be assumed, and + /// some parameters/arguments might need to be filtered. + /// We return the fun id, its generics, and filtering information for the + /// arguments. + /// + /// TODO: should we always erase the regions? + #[allow(clippy::too_many_arguments)] + pub(crate) fn translate_fun_decl_id_with_args( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + def_id: &hax::DefId, + substs: &Vec, + args: Option<&Vec>, + trait_refs: &Vec, + trait_info: &Option, + ) -> Result { + let rust_id = def_id.rust_def_id.unwrap(); + let name = self.t_ctx.def_id_to_name(def_id); + let is_local = rust_id.is_local(); + + // Check if this function is a actually `panic` + if name.equals_ref_name(&assumed::PANIC_NAME) + || name.equals_ref_name(&assumed::BEGIN_PANIC_NAME) + || name.equals_ref_name(&assumed::ASSERT_FAILED_NAME) + { + return Ok(SubstFunIdOrPanic::Panic); + } + + // There is something annoying: when going to MIR, the rust compiler + // sometimes introduces very low-level functions, which we need to + // catch early - in particular, before we start translating types and + // arguments, because we won't be able to translate some of them. + if name.equals_ref_name(&assumed::BOX_FREE_NAME) { + assert!(!is_local); + + // This deallocates a box. + // It should have two type parameters: + // - the type of the boxed value + // - the type of a global allocator (which we ignore) + assert!(substs.len() == 2); + assert!(trait_refs.is_empty()); + + // Translate the type parameter + let t_ty = if let hax::GenericArg::Type(ty) = substs.get(0).unwrap() { + self.translate_ty(span, erase_regions, ty)? + } else { + unreachable!() + }; + + let args = args + .map(|args| { + assert!(args.len() == 2); + // Translate the first argument - note that we use a special + // function to translate it: the operand should be of the form: + // `move b.0`, and if it is the case it will return `move b` + let arg = &args[0]; + let t_arg = self.translate_move_box_first_projector_operand(span, arg)?; + Ok(vec![t_arg]) + }) + .transpose()?; + + // Return + let func = FnPtr { + func: FunIdOrTraitMethodRef::mk_assumed(AssumedFunId::BoxFree), + generics: GenericArgs::new_from_types(vec![t_ty]), + trait_and_method_generic_args: None, + }; + let sfid = SubstFunId { func, args }; + Ok(SubstFunIdOrPanic::Fun(sfid)) + } else { + // Retrieve the lists of used parameters, in case of non-local + // definitions + let (used_type_args, used_args) = if is_local { + (Option::None, Option::None) + } else { + match assumed::function_to_info(&name) { + Option::None => (Option::None, Option::None), + Option::Some(used) => ( + Option::Some(used.used_type_params), + Option::Some(used.used_args), + ), + } + }; + + // Translate the type parameters + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + used_type_args, + substs, + trait_refs, + )?; + + // Translate the arguments + let args = args + .map(|args| self.translate_arguments(span, used_args, args)) + .transpose()?; + + // Check if the function is considered primitive: primitive + // functions benefit from special treatment. + let is_prim = if is_local { + false + } else { + assumed::get_fun_id_from_name(&name).is_some() + }; + + // Trait information + trace!( + "Trait information:\n- def_id: {:?}\n- impl source:\n{:?}", + rust_id, + trait_info + ); + trace!( + "Method traits:\n- def_id: {:?}\n- traits:\n{:?}", + rust_id, + trait_refs + ); + + if !is_prim { + // Two cases depending on whether we call a trait method or not + match trait_info { + Option::None => { + // "Regular" function call + let def_id = self.translate_fun_decl_id(rust_id); + let func = FunIdOrTraitMethodRef::Fun(FunId::Regular(def_id)); + let func = FnPtr { + func, + generics, + trait_and_method_generic_args: None, + }; + let sfid = SubstFunId { func, args }; + Ok(SubstFunIdOrPanic::Fun(sfid)) + } + Option::Some(trait_info) => { + // Trait method + let rust_id = def_id.rust_def_id.unwrap(); + let impl_source = self.translate_trait_impl_source( + span, + erase_regions, + &trait_info.impl_source, + )?; + // The impl source should be Some(...): trait markers (that we may + // eliminate) don't have methods. + let impl_source = impl_source.unwrap(); + + trace!("{:?}", rust_id); + + let trait_method_fun_id = self.translate_fun_decl_id(rust_id); + let method_name = self.t_ctx.translate_trait_item_name(rust_id); + + // Compute the concatenation of all the generic arguments which were given to + // the function (trait arguments + method arguments). + let trait_and_method_generic_args = { + let (regions, types, const_generics) = self.translate_substs( + span, + erase_regions, + None, + &trait_info.all_generics, + )?; + + // When concatenating the trait refs we have to be careful: + // - if we refer to an implementation, we must concatenate the + // trait references given to the impl source + // - if we refer to a clause, we must retrieve the + // parent trait clauses. + let trait_refs = match &impl_source.trait_id { + TraitInstanceId::TraitImpl(_) => impl_source + .generics + .trait_refs + .iter() + .chain(generics.trait_refs.iter()) + .cloned() + .collect(), + _ => impl_source + .trait_decl_ref + .generics + .trait_refs + .iter() + .chain(generics.trait_refs.iter()) + .cloned() + .collect(), + }; + Some(GenericArgs { + regions, + types, + const_generics, + trait_refs, + }) + }; + + let func = FunIdOrTraitMethodRef::Trait( + impl_source, + method_name, + trait_method_fun_id, + ); + let func = FnPtr { + func, + generics, + trait_and_method_generic_args, + }; + let sfid = SubstFunId { func, args }; + Ok(SubstFunIdOrPanic::Fun(sfid)) + } + } + } else { + // Primitive function. + // + // Note that there are subtleties with regards to the way types parameters + // are translated, because some functions are actually traits, where the + // types are used for the resolution. For instance, the following: + // `core::ops::deref::Deref::>::deref` + // is translated to: + // `box_deref` + // (the type parameter is not `Box` but `T`). + assert!(trait_info.is_none()); + + let aid = assumed::get_fun_id_from_name(&name).unwrap(); + + // Note that some functions are actually traits (deref, index, etc.): + // we assume that they are called only on a limited set of types + // (ex.: box, vec...). + // For those trait functions, we need a custom treatment to retrieve + // and check the type information. + // For instance, derefencing boxes generates MIR of the following form: + // ``` + // _2 = as Deref>::deref(move _3) + // ``` + // We have to retrieve the type `Box` and check that it is of the + // form `Box` (and we generate `box_deref`). + match aid { + AssumedFunId::BoxNew => { + // Nothing to do + } + AssumedFunId::BoxFree => { + // Special case handled elsewhere + unreachable!(); + } + AssumedFunId::ArrayIndexShared + | AssumedFunId::ArrayIndexMut + | AssumedFunId::ArrayToSliceShared + | AssumedFunId::ArrayToSliceMut + | AssumedFunId::ArrayRepeat + | AssumedFunId::SliceIndexShared + | AssumedFunId::SliceIndexMut => { + // Those cases are introduced later, in micro-passes, by desugaring + // projections (for ArrayIndex and ArrayIndexMut for instnace) and= + // operations (for ArrayToSlice for instance) to function calls. + unreachable!() + } + }; + + let func = FnPtr { + func: FunIdOrTraitMethodRef::Fun(FunId::Assumed(aid)), + generics, + trait_and_method_generic_args: None, + }; + let sfid = SubstFunId { func, args }; + Ok(SubstFunIdOrPanic::Fun(sfid)) } } } @@ -1096,41 +1044,44 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// We return an option, because we ignore some statements (`Nop`, `StorageLive`...) fn translate_statement( &mut self, - body: &Body<'tcx>, - statement: &Statement<'tcx>, - ) -> Result> { + body: &hax::MirBody<()>, + statement: &hax::Statement, + ) -> Result, Error> { trace!("About to translate statement (MIR) {:?}", statement); + let span = statement.source_info.span.rust_span; use std::ops::Deref; - let t_statement: Option = match &statement.kind { + use hax::StatementKind; + let t_statement: Option = match &*statement.kind { StatementKind::Assign(assign) => { let (place, rvalue) = assign.deref(); - let t_place = self.translate_place(place); - let t_rvalue = self.translate_rvalue(rvalue); + let t_place = self.translate_place(span, place)?; + let t_rvalue = + self.translate_rvalue(statement.source_info.span.rust_span, rvalue)?; - Some(ast::RawStatement::Assign(t_place, t_rvalue)) + Some(RawStatement::Assign(t_place, t_rvalue)) } StatementKind::FakeRead(info) => { let (_read_cause, place) = info.deref(); - let t_place = self.translate_place(place); + let t_place = self.translate_place(span, place)?; - Some(ast::RawStatement::FakeRead(t_place)) + Some(RawStatement::FakeRead(t_place)) } StatementKind::PlaceMention(place) => { // Simply accesses a place. Introduced for instance in place // of `let _ = ...`. We desugar it to a fake read. - let t_place = self.translate_place(place); + let t_place = self.translate_place(span, place)?; - Some(ast::RawStatement::FakeRead(t_place)) + Some(RawStatement::FakeRead(t_place)) } StatementKind::SetDiscriminant { place, variant_index, } => { - let t_place = self.translate_place(place); + let t_place = self.translate_place(span, place)?; let variant_id = translate_variant_id(*variant_index); - Some(ast::RawStatement::SetDiscriminant(t_place, variant_id)) + Some(RawStatement::SetDiscriminant(t_place, variant_id)) } StatementKind::StorageLive(_) => { // We ignore StorageLive @@ -1138,7 +1089,7 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } StatementKind::StorageDead(local) => { let var_id = self.get_local(local).unwrap(); - Some(ast::RawStatement::StorageDead(var_id)) + Some(RawStatement::StorageDead(var_id)) } StatementKind::Retag(_, _) => { // This is for the stacked borrows @@ -1153,18 +1104,18 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { None } StatementKind::Coverage(_) => { - unimplemented!(); + error_or_panic!(self, span, "Unsupported statement kind: coverage"); } StatementKind::Nop => { // We ignore this statement None } StatementKind::Deinit(place) => { - let t_place = self.translate_place(place); - Some(ast::RawStatement::Deinit(t_place)) + let t_place = self.translate_place(span, place)?; + Some(RawStatement::Deinit(t_place)) } StatementKind::Intrinsic(_) => { - unimplemented!(); + error_or_panic!(self, span, "Unsupported statement kind: intrinsic"); } StatementKind::ConstEvalCounter => { // See the doc: only used in the interpreter, to check that @@ -1180,9 +1131,9 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { Some(t_statement) => { let meta = self .t_ctx - .translate_meta_from_source_info(&body.source_scopes, statement.source_info); + .translate_meta_from_source_info(&body.source_scopes, &statement.source_info); - Ok(Some(ast::Statement::new(meta, t_statement))) + Ok(Some(Statement::new(meta, t_statement))) } } } @@ -1190,60 +1141,75 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// Translate a terminator fn translate_terminator( &mut self, - body: &Body<'tcx>, - terminator: &Terminator<'tcx>, - ) -> Result { + body: &hax::MirBody<()>, + terminator: &hax::Terminator, + ) -> Result { trace!("About to translate terminator (MIR) {:?}", terminator); + let span = terminator.source_info.span.rust_span; // Compute the meta information beforehand (we might need it to introduce // intermediate statements - we desugar some terminators) let meta = self .t_ctx - .translate_meta_from_source_info(&body.source_scopes, terminator.source_info); + .translate_meta_from_source_info(&body.source_scopes, &terminator.source_info); // Translate the terminator - let t_terminator: ast::RawTerminator = match &terminator.kind { + use hax::TerminatorKind; + let t_terminator: RawTerminator = match &terminator.kind { TerminatorKind::Goto { target } => { let target = self.translate_basic_block(body, *target)?; - ast::RawTerminator::Goto { target } + RawTerminator::Goto { target } } TerminatorKind::SwitchInt { discr, targets } => { // Translate the operand which gives the discriminant - let (discr, discr_ty) = self.translate_operand_with_type(discr); + let (discr, discr_ty) = self.translate_operand_with_type(span, discr)?; // Translate the switch targets let targets = self.translate_switch_targets(body, &discr_ty, targets)?; - ast::RawTerminator::Switch { discr, targets } + RawTerminator::Switch { discr, targets } } TerminatorKind::Resume => { // This is used to correctly unwind. We shouldn't get there: if // we panic, the state gets stuck. - unreachable!(); + error_or_panic!(self, span, "Unexpected terminator: resume"); } - TerminatorKind::Return => ast::RawTerminator::Return, - TerminatorKind::Unreachable => ast::RawTerminator::Unreachable, + TerminatorKind::Return => RawTerminator::Return, + TerminatorKind::Unreachable => RawTerminator::Unreachable, TerminatorKind::Terminate => unimplemented!(), TerminatorKind::Drop { place, target, unwind: _, // We consider that panic is an error, and don't model unwinding replace: _, - } => ast::RawTerminator::Drop { - place: self.translate_place(place), + } => RawTerminator::Drop { + place: self.translate_place(span, place)?, target: self.translate_basic_block(body, *target)?, }, TerminatorKind::Call { - func, + fun_id, + substs, args, destination, target, + trait_refs, + trait_info, unwind: _, // We consider that panic is an error, and don't model unwinding from_hir_call: _, fn_span: _, } => { - trace!("Call: func: {:?}", func); - self.translate_function_call(body, func, args, destination, target)? + trace!("Call: func: {:?}", fun_id.rust_def_id); + self.translate_function_call( + span, + body, + fun_id, + substs, + args, + destination, + target, + trait_refs, + trait_info, + )? } TerminatorKind::Assert { cond, @@ -1252,9 +1218,9 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { target, unwind: _, // We consider that panic is an error, and don't model unwinding } => { - let cond = self.translate_operand(cond); + let cond = self.translate_operand(span, cond)?; let target = self.translate_basic_block(body, *target)?; - ast::RawTerminator::Assert { + RawTerminator::Assert { cond, expected: *expected, target, @@ -1266,10 +1232,10 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { resume_arg: _, drop: _, } => { - unimplemented!(); + error_or_panic!(self, span, "Unsupported terminator: yield"); } TerminatorKind::GeneratorDrop => { - unimplemented!(); + error_or_panic!(self, span, "Unsupported terminator: generator drop"); } TerminatorKind::FalseEdge { real_target, @@ -1288,7 +1254,7 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { // Also note that they are used in some passes, and not in some others // (they are present in mir_promoted, but not mir_optimized). let target = self.translate_basic_block(body, *real_target)?; - ast::RawTerminator::Goto { target } + RawTerminator::Goto { target } } TerminatorKind::FalseUnwind { real_target, @@ -1296,65 +1262,43 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } => { // We consider this to be a goto let target = self.translate_basic_block(body, *real_target)?; - ast::RawTerminator::Goto { target } + RawTerminator::Goto { target } } TerminatorKind::InlineAsm { .. } => { - // This case should have been eliminated during the registration phase - unreachable!(); + error_or_panic!(self, span, "Inline assembly is not supported"); } }; // Add the meta information - Ok(ast::Terminator::new(meta, t_terminator)) + Ok(Terminator::new(meta, t_terminator)) } /// Translate switch targets fn translate_switch_targets( &mut self, - body: &Body<'tcx>, - switch_ty: &ty::ETy, - targets: &mir::SwitchTargets, - ) -> Result { + body: &hax::MirBody<()>, + switch_ty: &Ty, + targets: &hax::SwitchTargets, + ) -> Result { trace!("targets: {:?}", targets); - let targets_vec: Vec<(u128, BasicBlock)> = targets.iter().map(|(v, b)| (v, b)).collect(); - - match switch_ty { - ty::Ty::Literal(ty::LiteralTy::Bool) => { - // This is an: `if ... then ... else ...` - assert!(targets_vec.len() == 1); - // It seems the block targets are inverted - let (test_val, otherwise_block) = targets_vec[0]; - - assert!(test_val == 0); - - // It seems the block targets are inverted - let if_block = self.translate_basic_block(body, targets.otherwise())?; - let otherwise_block = self.translate_basic_block(body, otherwise_block)?; - - Ok(ast::SwitchTargets::If(if_block, otherwise_block)) - } - ty::Ty::Literal(ty::LiteralTy::Integer(int_ty)) => { - // This is a: switch(int). - // Convert all the test values to the proper values. - let mut targets_map: Vec<(v::ScalarValue, ast::BlockId::Id)> = Vec::new(); - for (v, tgt) in targets_vec { - // We need to reinterpret the bytes (`v as i128` is not correct) - let raw: [u8; 16] = v.to_le_bytes(); - let v = v::ScalarValue::from_le_bytes(*int_ty, raw); - let tgt = self.translate_basic_block(body, tgt)?; - targets_map.push((v, tgt)); - } - let otherwise_block = self.translate_basic_block(body, targets.otherwise())?; - - Ok(ast::SwitchTargets::SwitchInt( - *int_ty, - targets_map, - otherwise_block, - )) + match targets { + hax::SwitchTargets::If(if_block, then_block) => { + let if_block = self.translate_basic_block(body, *if_block)?; + let then_block = self.translate_basic_block(body, *then_block)?; + Ok(SwitchTargets::If(if_block, then_block)) } - _ => { - trace!("Unexpected switch_ty: {}", switch_ty.variant_name()); - unreachable!(); + hax::SwitchTargets::SwitchInt(_, targets_map, otherwise) => { + let int_ty = *switch_ty.as_literal().as_integer(); + let targets_map: Vec<(ScalarValue, BlockId::Id)> = targets_map + .iter() + .map(|(v, tgt)| { + let v = ScalarValue::from_le_bytes(int_ty, v.data_le_bytes); + let tgt = self.translate_basic_block(body, *tgt)?; + Ok((v, tgt)) + }) + .try_collect()?; + let otherwise = self.translate_basic_block(body, *otherwise)?; + Ok(SwitchTargets::SwitchInt(int_ty, targets_map, otherwise)) } } } @@ -1363,228 +1307,79 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// Note that `body` is the body of the function being translated, not of the /// function referenced in the function call: we need it in order to translate /// the blocks we go to after the function call returns. + #[allow(clippy::too_many_arguments)] fn translate_function_call( &mut self, - body: &mir::Body<'tcx>, - func: &Operand<'tcx>, - args: &Vec>, - destination: &Place<'tcx>, - target: &Option, - ) -> Result { + span: rustc_span::Span, + body: &hax::MirBody<()>, + def_id: &hax::DefId, + substs: &Vec, + args: &Vec, + destination: &hax::Place, + target: &Option, + trait_refs: &Vec, + trait_info: &Option, + ) -> Result { trace!(); + let rust_id = def_id.rust_def_id.unwrap(); // Translate the function operand - should be a constant: we don't // support closures for now - trace!("func: {:?}", func); - - let tcx = self.t_ctx.tcx; - - // Retrieve the function's identifier and instantiation - let (def_id, substs) = get_function_from_operand(func); - - // Translate the name to check if is is `core::panicking::panic` - let name = function_def_id_to_name(tcx, def_id); + trace!("func: {:?}", rust_id); - // If the call is `panic!`, then the target is `None`. - // I don't know in which other cases it can be `None`. - if name.equals_ref_name(&assumed::PANIC_NAME) - || name.equals_ref_name(&assumed::BEGIN_PANIC_NAME) - { - assert!(!def_id.is_local()); - assert!(target.is_none()); - - // We ignore the arguments - Ok(ast::RawTerminator::Panic) - } else { - assert!(target.is_some()); - let next_block = target.unwrap(); - - // Translate the target - let lval = self.translate_place(destination); - let next_block = self.translate_basic_block(body, next_block)?; - - // There is something annoying: when going to MIR, the rust compiler - // sometimes introduces very low-level functions, which we need to - // catch early - in particular, before we start translating types and - // arguments, because we won't be able to translate some of them. - if name.equals_ref_name(&assumed::BOX_FREE_NAME) { - assert!(!def_id.is_local()); - - // This deallocates a box. - // It should have two type parameters: - // - the type of the boxed value - // - the type of a global allocator (which we ignore) - // The arguments should be of the form: - // - `move b.0` (the allocated value) - // - `move b.1` (the global allocated) - // where b is of type `Box` (boxes are pairs actually) - // We replace that with: `move b` - assert!(substs.len() == 2); - assert!(args.len() == 2); - - // Translate the type parameter - let ty = substs.get(0).unwrap().expect_ty(); - let t_ty = self.translate_ety(&ty)?; - - // Translate the first argument - note that we use a special - // function to translate it: the operand should be of the form: - // `move b.0`, and if it is the case it will return `move b` - let arg = &args[0]; - let t_arg = self.translate_move_box_first_projector_operand(arg); - - // Return - let call = ast::Call { - func: ast::FunId::Assumed(ast::AssumedFunId::BoxFree), - region_args: vec![], - type_args: vec![t_ty], - const_generic_args: vec![], - args: vec![t_arg], + // Translate the function id, with its parameters + let erase_regions = true; + let fid = self.translate_fun_decl_id_with_args( + span, + erase_regions, + def_id, + substs, + Some(args), + trait_refs, + trait_info, + )?; + + match fid { + SubstFunIdOrPanic::Panic => { + // If the call is `panic!`, then the target is `None`. + // I don't know in which other cases it can be `None`. + assert!(target.is_none()); + + // We ignore the arguments + Ok(RawTerminator::Panic) + } + SubstFunIdOrPanic::Fun(fid) => { + let next_block = target.unwrap_or_else(|| { + panic!("Expected a next block after the call to {:?}.\n\nSubsts: {:?}\n\nArgs: {:?}:", rust_id, substs, args) + }); + + // Translate the target + let lval = self.translate_place(span, destination)?; + let next_block = self.translate_basic_block(body, next_block)?; + + let call = Call { + func: fid.func, + args: fid.args.unwrap(), dest: lval, }; - Ok(ast::RawTerminator::Call { + + Ok(RawTerminator::Call { call, target: next_block, }) - } else { - // Retrieve the lists of used parameters, in case of non-local - // definitions - let (used_type_args, used_args) = if def_id.is_local() { - (Option::None, Option::None) - } else { - match assumed::function_to_info(&name) { - Option::None => (Option::None, Option::None), - Option::Some(used) => ( - Option::Some(used.used_type_params), - Option::Some(used.used_args), - ), - } - }; - - // Translate the type parameters - let (region_args, type_args, const_generic_args) = - self.translate_subst_generic_args_in_body(used_type_args, substs)?; - - // Translate the arguments - let args = self.translate_arguments(used_args, args); - - // Check if the function is considered primitive: primitive - // functions benefit from special treatment. - let name = function_def_id_to_name(tcx, def_id); - let is_prim = if def_id.is_local() { - false - } else { - assumed::get_fun_id_from_name(&name, &type_args).is_some() - }; - - if !is_prim { - // Retrieve the def id - let def_id = self.translate_fun_decl_id(def_id); - - let func = ast::FunId::Regular(def_id); - let call = ast::Call { - func, - region_args, - type_args, - const_generic_args, - args, - dest: lval, - }; - - Ok(ast::RawTerminator::Call { - call, - target: next_block, - }) - } else { - // Primitive function. - // - // Note that there are subtleties with regards to the way types parameters - // are translated, because some functions are actually traits, where the - // types are used for the resolution. For instance, the following: - // `core::ops::deref::Deref::>::deref` - // is translated to: - // `box_deref` - // (the type parameter is not `Box` but `T`). - translate_primitive_function_call( - tcx, - def_id, - region_args, - type_args, - const_generic_args, - args, - lval, - next_block, - ) - } - } - } - } - - /// Translate a parameter substitution used inside a function body. - /// - /// Note that the regions parameters are expected to have been erased. - fn translate_subst_generic_args_in_body( - &mut self, - used_args: Option>, - substs: &rustc_middle::ty::subst::InternalSubsts<'tcx>, - ) -> Result<(Vec, Vec, Vec)> { - let substs: Vec> = match used_args { - Option::None => substs.iter().collect(), - Option::Some(used_args) => { - assert!(substs.len() == used_args.len()); - substs - .iter() - .zip(used_args.into_iter()) - .filter_map(|(param, used)| if used { Some(param) } else { None }) - .collect() - } - }; - - let mut t_args_regions = Vec::new(); - let mut t_args_tys = Vec::new(); - let mut t_args_cgs = Vec::new(); - for param in substs.iter() { - match param.unpack() { - rustc_middle::ty::subst::GenericArgKind::Type(param_ty) => { - // Simply translate the type - let t_param_ty = self.translate_ety(¶m_ty)?; - t_args_tys.push(t_param_ty); - } - rustc_middle::ty::subst::GenericArgKind::Lifetime(region) => { - t_args_regions.push(translate_erased_region(region.kind())); - } - rustc_middle::ty::subst::GenericArgKind::Const(c) => { - let t_cg = self.translate_const_kind_as_const_generic(c); - t_args_cgs.push(t_cg); - } } } - - Ok((t_args_regions, t_args_tys, t_args_cgs)) - } - - /// Translate a parameter substitution used inside a function body. - /// - /// Note that the regions parameters are expected to have been erased. - fn translate_subst_in_body( - &mut self, - substs: &rustc_middle::ty::List>, - ) -> Result> { - let mut t_args_tys = Vec::new(); - - for param in substs.iter() { - t_args_tys.push(self.translate_ety(¶m)?); - } - Ok(t_args_tys) } /// Evaluate function arguments in a context, and return the list of computed /// values. fn translate_arguments( &mut self, + span: rustc_span::Span, used_args: Option>, - args: &Vec>, - ) -> Vec { - let args: Vec<&Operand<'tcx>> = match used_args { + args: &Vec, + ) -> Result, Error> { + let args: Vec<&hax::Operand> = match used_args { Option::None => args.iter().collect(), Option::Some(used_args) => { assert!(args.len() == used_args.len()); @@ -1595,45 +1390,60 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } }; - let mut t_args: Vec = Vec::new(); + let mut t_args: Vec = Vec::new(); for arg in args { // There should only be moved arguments, or constants match arg { - mir::Operand::Move(_) | mir::Operand::Constant(_) => { + hax::Operand::Move(_) | hax::Operand::Constant(_) => { // OK } - mir::Operand::Copy(_) => { + hax::Operand::Copy(_) => { unreachable!(); } } // Translate - let op = self.translate_operand(arg); + let op = self.translate_operand(span, arg)?; t_args.push(op); } - t_args + Ok(t_args) } - fn translate_body(mut self, local_id: LocalDefId, arg_count: usize) -> Result { + fn translate_body(mut self, local_id: LocalDefId, arg_count: usize) -> Result { let tcx = self.t_ctx.tcx; + // Retrive the body let body = get_mir_for_def_id_and_level(tcx, local_id, self.t_ctx.mir_level); - // Compute the meta information - let meta = self.translate_meta_from_rspan(body.span); + // Here, we have to create a MIR state, which contains the body + let state = hax::state::State::new_from_mir( + tcx, + hax::options::Options { + inline_macro_calls: Vec::new(), + }, + // Yes, we have to clone, this is annoying: we end up cloning the body twice + body.clone(), + // Owner id + local_id.to_def_id(), + ); + // Translate + let body: hax::MirBody<()> = body.sinto(&state); // Initialize the local variables trace!("Translating the body locals"); - self.translate_body_locals(body)?; + self.translate_body_locals(&body)?; // Translate the expression body trace!("Translating the expression body"); - self.translate_transparent_expression_body(body)?; + self.translate_transparent_expression_body(&body)?; + + // Compute the meta information + let meta = self.translate_meta_from_rspan(body.span); // We need to convert the blocks map to an index vector // We clone things while we could move them... - let mut blocks = ast::BlockId::Vector::new(); + let mut blocks = BlockId::Vector::new(); for (id, block) in self.blocks { use crate::id_vector::ToUsize; // Sanity check to make sure we don't mess with the indices @@ -1642,169 +1452,199 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } // Create the body - Ok(ast::ExprBody { + Ok(ExprBody { meta, arg_count, locals: self.vars, body: blocks, }) } -} -impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Translate a function's signature, and initialize a body translation context /// at the same time - the function signature gives us the list of region and /// type parameters, that we put in the translation context. - fn translate_function_signature<'ctx1>( - &'ctx1 mut self, - def_id: DefId, - ) -> (BodyTransCtx<'tcx, 'ctx1, 'ctx>, ast::FunSig) { - let tcx = self.tcx; + fn translate_function_signature(&mut self, def_id: DefId) -> Result { + let tcx = self.t_ctx.tcx; + let erase_regions = false; + let span = self.t_ctx.tcx.def_span(def_id); // Retrieve the function signature, which includes the lifetimes - let signature = tcx.fn_sig(def_id).subst_identity(); - - // Instantiate the signature's bound region variables (the signature - // is wrapped in a [`Binder`](rustc_middle::ty::Binder). This is inspired by - // [`liberate_late_bound_regions`](TyCtx::liberate_late_bound_regions). - // The rationale is as follows: - // - it seems liberate_late_bound_regions is a proper way of retrieving - // a signature where all the bound variables have been replaced with - // free variables, so that we can study it easily (without having, for - // instance, to deal with DeBruijn indices) - // - my understanding of why it is enough to bind late-bound regions: the - // early bound regions are not bound here (they are free), because - // they reference regions introduced by the `impl` block (if this definition - // is defined in an `impl` block - otherwise there are no early bound variables) - // while the late bound regions are introduced by the function itself. - // For example, in: - // ``` - // impl<'a> Foo<'a> { - // fn bar<'b>(...) -> ... { ... } - // } - // ``` - // `'a` is early-bound while `'b` is late-bound. - // - we can't just use `liberate_late_bound_regions`, because we want to know - // in which *order* the regions were bound - it is mostly a matter of stability - // of the translation: we will have to generate one backward function per - // region, and we need to know in which order to introduce those backward - // functions. - // Actually, `liberate_late_bound_regions` returns a b-tree: maybe the - // order between the bound regions is such that when iterating over the - // keys of this tree, we iterator over the bound regions in the order in - // which they are bound. As we are not too sure about that, we prefer - // reimplementing our own function, which is quite simple. - - // We need a body translation context to keep track of all the variables - let mut bt_ctx = BodyTransCtx::new(def_id, self); - - // **Sanity checks on the HIR** - generics::check_function_generics(tcx, def_id); - - // Start by translating the "normal" substitution (which lists the function's - // parameters). As written above, this substitution contains all the type - // variables, and the early-bound regions, but not the late-bound ones. - // TODO: we do something similar in `translate_function` + let rsignature: rustc_middle::ty::Binder<'tcx, rustc_middle::ty::FnSig<'tcx>> = + if tcx.is_closure(def_id) { + // TODO: + // ``` + // error: internal compiler error: compiler/rustc_hir_analysis/src/collect.rs:1118:13: + // to get the signature of a closure, use `substs.as_closure().sig()` not `fn_sig()` + // ``` + // + // We need to have a map from def ids to signatures, for the + // closures. We also need to replace the vectors of type variables, + // regions, etc. with maps, because the indices will not always + // start at 0. + log::trace!("{:?}", tcx.type_of(def_id)); + error_or_panic!(self, span, "Closures are not supported yet"); + } else { + let fn_sig = tcx.fn_sig(def_id); + trace!("Fun sig: {:?}", fn_sig); + // There is an early binder for the early-bound regions, that + // we ignore, and a binder for the late-bound regions, that we + // keep. + fn_sig.subst_identity() + }; + + // The parameters (and in particular the lifetimes) are split between + // early bound and late bound parameters. See those blog posts for explanations: + // https://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/ + // https://smallcultfollowing.com/babysteps/blog/2013/11/04/intermingled-parameter-lists/ + // Note that only lifetimes can be late bound. + // + // [TyCtxt.generics_of] gives us the early-bound parameters + // The late-bounds parameters are bound in the [Binder] returned by + // [TyCtxt.type_of]. + + // Start by translating the early-bound parameters (those are contained by `substs`). let fun_type = tcx.type_of(def_id).subst_identity(); - let substs = match fun_type.kind() { - TyKind::FnDef(_def_id, substs_ref) => substs_ref, + let rsubsts = match fun_type.kind() { + ty::TyKind::FnDef(_def_id, substs_ref) => substs_ref, _ => { unreachable!() } }; - - for param in substs.iter() { - match param.unpack() { - rustc_middle::ty::subst::GenericArgKind::Type(param_ty) => { - // This type should be a param type - match param_ty.kind() { - TyKind::Param(param_ty) => { - bt_ctx.push_type_var(param_ty.index, param_ty.name.to_ident_string()); - } - _ => { - unreachable!(); - } + let substs = rsubsts.sinto(&self.hax_state); + + // Some debugging information: + trace!("Def id: {def_id:?}:\n\n- substs:\n{substs:?}\n\n- generics:\n{:?}\n\n- signature bound vars:\n{:?}\n\n- signature:\n{:?}\n", + tcx.generics_of(def_id), rsignature.bound_vars(), rsignature); + + // Add the *early-bound* parameters. + self.translate_generic_params_from_hax(span, &substs)?; + + // + // Add the *late-bound* parameters (bound in the signature, can only be lifetimes) + // + let signature: hax::MirPolyFnSig = rsignature.sinto(&self.hax_state); + let is_unsafe = match signature.value.unsafety { + hax::Unsafety::Unsafe => true, + hax::Unsafety::Normal => false, + }; + let bvar_names = signature + .bound_vars + .into_iter() + .map(|bvar| { + // There should only be regions in the late-bound parameters + use hax::BoundVariableKind; + match bvar { + BoundVariableKind::Region(br) => Ok(translate_bound_region_kind_name(&br)), + BoundVariableKind::Ty(_) | BoundVariableKind::Const => { + error_or_panic!( + self, + span, + format!("Unexpected bound variable: {:?}", bvar) + ) } } - rustc_middle::ty::subst::GenericArgKind::Lifetime(region) => { - let name = translate_region_name(®ion); - bt_ctx.push_region(*region, name); + }) + .try_collect()?; + self.push_bound_regions_group(bvar_names); + + let fun_kind = self.t_ctx.get_fun_kind(def_id); + + // Add the trait clauses + self.while_registering_trait_clauses(&mut |ctx| { + // Add the ctx trait clause if it is a trait decl item + match &fun_kind { + FunKind::Regular => (), + FunKind::TraitMethodImpl { impl_id, .. } => { + ctx.add_trait_impl_self_trait_clause(*impl_id)?; } - rustc_middle::ty::subst::GenericArgKind::Const(c) => { - // The type should be primitive, meaning it shouldn't contain - // variables, etc. (we could use an empty context). - let ty = bt_ctx.translate_ety(&c.ty()).unwrap(); - let ty = ty.to_literal(); - match c.kind() { - rustc_middle::ty::ConstKind::Param(cp) => { - bt_ctx.push_const_generic_var(cp.index, ty, cp.name.to_ident_string()); - } - _ => unreachable!(), - } + FunKind::TraitMethodProvided(..) | FunKind::TraitMethodDecl(..) => { + // This is a trait decl item + let trait_id = tcx.trait_of_item(def_id).unwrap(); + ctx.add_self_trait_clause(trait_id)?; } } - } - - // Instantiate the regions bound in the signature, and generate a mapping - // while doing so (the mapping uses a linked hash map so that we remember - // in which order we introduced the regions). - // Note that replace_late_bound_regions` returns a map from bound regions to - // regions, but it is unclear whether this map preserves the order in which - // the regions were introduced (the map is a BTreeMap, so I guess it depends - // on how the the bound variables were numbered) and it doesn't cost us - // much to create this mapping ourselves. - let (signature, late_bound_regions) = - generics::replace_late_bound_regions(tcx, signature, def_id); - - // Introduce identifiers and translated regions for the late-bound regions - for (_, region) in &late_bound_regions { - let name = translate_region_name(region); - bt_ctx.push_region(**region, name); - } - trace!( - "# Early and late bound regions:\n{}", - iterator_to_string( - &|x: &ty::RegionVar| x.to_string(), - bt_ctx.region_vars.iter() - ) - ); - trace!( - "# Type variables:\n{}", - iterator_to_string(&|x: &ty::TypeVar| x.to_string(), bt_ctx.type_vars.iter()) - ); + // Translate the predicates (in particular, the trait clauses) + match &fun_kind { + FunKind::Regular | FunKind::TraitMethodImpl { .. } => { + ctx.translate_predicates_of(None, def_id)?; + } + FunKind::TraitMethodProvided(trait_decl_id, ..) + | FunKind::TraitMethodDecl(trait_decl_id, ..) => { + ctx.translate_predicates_of(Some(*trait_decl_id), def_id)?; + } + } - // Now that we instantiated all the binders and introduced identifiers for - // all the variables, we can translate the function's signature. - let inputs: Vec = Vec::from_iter( - signature - .inputs() - .iter() - .map(|ty| bt_ctx.translate_sig_ty(ty).unwrap()), - ); - let output = bt_ctx.translate_sig_ty(&signature.output()).unwrap(); + // Solve the unsolved obligations + ctx.solve_trait_obligations_in_trait_clauses(span); + Ok(()) + })?; + + // Translate the signature + let signature = signature.value; + trace!("signature of {def_id:?}:\n{:?}", signature); + let inputs: Vec = signature + .inputs + .iter() + .map(|ty| self.translate_ty(span, erase_regions, ty)) + .try_collect()?; + let output = self.translate_ty(span, erase_regions, &signature.output)?; trace!( "# Input variables types:\n{}", - iterator_to_string(&|x| bt_ctx.format_object(x), inputs.iter()) + iterator_to_string(&|x| self.format_object(x), inputs.iter()) ); - trace!("# Output variable type:\n{}", bt_ctx.format_object(&output)); - - let sig = ast::FunSig { - region_params: bt_ctx.region_vars.clone(), - num_early_bound_regions: late_bound_regions.len(), - regions_hierarchy: RegionGroups::new(), // Hierarchy not yet computed - type_params: bt_ctx.type_vars.clone(), - const_generic_params: bt_ctx.const_generic_vars.clone(), + trace!("# Output variable type:\n{}", self.format_object(&output)); + + let mut parent_params_info = self.get_function_parent_params_info(def_id); + // If this is a trait decl method, we need to adjust the number of parent clauses + if matches!( + &fun_kind, + FunKind::TraitMethodProvided(..) | FunKind::TraitMethodDecl(..) + ) { + if let Some(info) = &mut parent_params_info { + // All the trait clauses are registered as parent (of Self) + // trait clauses, not as local trait clauses. + info.num_trait_clauses = 0; + } + } + + Ok(FunSig { + generics: self.get_generics(), + preds: self.get_predicates(), + is_unsafe, + parent_params_info, inputs, output, - }; + }) + } - (bt_ctx, sig) + fn get_function_parent_params_info(&mut self, def_id: DefId) -> Option { + let kind = self.t_ctx.get_fun_kind(def_id); + match kind { + FunKind::Regular => None, + FunKind::TraitMethodImpl { .. } + | FunKind::TraitMethodDecl { .. } + | FunKind::TraitMethodProvided { .. } => { + Some(self.get_parent_params_info(def_id).unwrap()) + } + } } +} +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Translate one function. pub(crate) fn translate_function(&mut self, rust_id: DefId) { + // TODO: for now, if there is an error while translating the signature + // of the function, we ignore the function altogether, while we should + // save somewhere that we failed to extract it. + if self.translate_function_aux(rust_id).is_err() { + // TODO + } + } + + /// Auxliary helper to properly handle errors, see [translate_function]. + pub fn translate_function_aux(&mut self, rust_id: DefId) -> Result<(), Error> { trace!("About to translate function:\n{:?}", rust_id); let def_id = self.translate_fun_decl_id(rust_id); let is_transparent = self.id_is_transparent(rust_id); @@ -1812,148 +1652,122 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { // Compute the meta information let meta = self.translate_meta_from_rid(rust_id); + // Initialize the body translation context + let mut bt_ctx = BodyTransCtx::new(rust_id, self); + // Translate the function name - let name = function_def_id_to_name(self.tcx, rust_id); + let name = bt_ctx + .t_ctx + .extended_def_id_to_name(&rust_id.sinto(&bt_ctx.hax_state)); + + // Check whether this function is a method declaration for a trait definition. + // If this is the case, it shouldn't contain a body. + let kind = bt_ctx.t_ctx.get_fun_kind(rust_id); + let is_trait_method_decl = match &kind { + FunKind::Regular + | FunKind::TraitMethodImpl { .. } + | FunKind::TraitMethodProvided(..) => false, + FunKind::TraitMethodDecl(..) => true, + }; - // Translate the function signature and initialize the body translation context - // at the same time (the signature gives us the region and type parameters, - // that we put in the translation context). + // Translate the function signature trace!("Translating function signature"); - let (bt_ctx, signature) = self.translate_function_signature(rust_id); + let signature = bt_ctx.translate_function_signature(rust_id)?; // Check if the type is opaque or transparent - let body = if !is_transparent || !rust_id.is_local() { - Option::None + let is_local = rust_id.is_local(); + + let body = if !is_transparent || !is_local || is_trait_method_decl { + None } else { - Option::Some( - bt_ctx - .translate_body(rust_id.expect_local(), signature.inputs.len()) - .unwrap(), - ) + match bt_ctx.translate_body(rust_id.expect_local(), signature.inputs.len()) { + Ok(body) => Some(body), + Err(_) => { + // Error case: we could have a variant for this + None + } + } }; // Save the new function self.fun_defs.insert( def_id, - ast::FunDecl { + FunDecl { meta, def_id, + is_local, name, signature, + kind, body, }, ); - } - /// Generate an expression body from a typed constant value. - fn global_generate_assignment_body( - &mut self, - ty: ty::ETy, - def_rid: DefId, - val: e::OperandConstantValue, - ) -> ast::ExprBody { - // Compute the meta information (we use the same everywhere) - let meta = self.translate_meta_from_rid(def_rid); - - // # Variables - // ret : ty - let var = ast::Var { - index: v::VarId::ZERO, - name: None, - ty: ty.clone(), - }; - // # Instructions - // ret := const (ty, val) - // return - let block = ast::BlockData { - statements: vec![ast::Statement::new( - meta, - ast::RawStatement::Assign( - e::Place::new(var.index), - e::Rvalue::Use(e::Operand::Const(ty, val)), - ), - )], - terminator: ast::Terminator::new(meta, ast::RawTerminator::Return), - }; - ast::ExprBody { - meta, - arg_count: 0, - locals: id_vector::Vector::from(vec![var]), - body: id_vector::Vector::from(vec![block]), - } + Ok(()) } /// Translate one global. pub(crate) fn translate_global(&mut self, rust_id: DefId) { + // TODO: for now, if there is an error while translating the parameters/ + // predicates of the global, we ignore the declaration altogether, while + // we should save somewhere that we failed to extract it. + if self.translate_global_aux(rust_id).is_err() { + // TODO + } + } + + /// Auxliary helper to properly handle errors, see [translate_global]. + pub fn translate_global_aux(&mut self, rust_id: DefId) -> Result<(), Error> { trace!("About to translate global:\n{:?}", rust_id); + let span = self.tcx.def_span(rust_id); let def_id = self.translate_global_decl_id(rust_id); - // Translate the global name - let name = global_def_id_to_name(self.tcx, rust_id); - // Compute the meta information let meta = self.translate_meta_from_rid(rust_id); let is_transparent = self.id_is_transparent(rust_id); + // Initialize the body translation context let mut bt_ctx = BodyTransCtx::new(rust_id, self); + let hax_state = &bt_ctx.hax_state; + + // Translate the global name + let name = bt_ctx + .t_ctx + .extended_def_id_to_name(&rust_id.sinto(hax_state)); trace!("Translating global type"); let mir_ty = bt_ctx.t_ctx.tcx.type_of(rust_id).subst_identity(); - let g_ty = bt_ctx.translate_ety(&mir_ty).unwrap(); - - let body = match (rust_id.is_local(), is_transparent) { - // It's a local and opaque global: we do not give it a body. - (true, false) => Option::None, + let erase_regions = false; // This doesn't matter: there shouldn't be any regions + let ty = bt_ctx.translate_ty(span, erase_regions, &mir_ty.sinto(hax_state))?; + let body = if rust_id.is_local() && is_transparent { // It's a local and transparent global: we extract its body as for functions. - (true, true) => Option::Some(bt_ctx.translate_body(rust_id.expect_local(), 0).unwrap()), - - // It is an external global. - // The fact that it is listed among the declarations to extract means that - // some local declaration depends on it. - // Consequently, we try to evaluate its value. - // If the evaluation succeeds, we generate a body. - // If the evaluation fails, we warn about the failure and generate an - // empty body. - // TODO: Perhaps the policy should depend on `static` (accept) VS - // `const` (reject) global ? Or force a successful translation, but - // translate only if it's transparent ? - (false, _) => { - let unev = rid_as_unevaluated_constant(rust_id); - match bt_ctx.t_ctx.tcx.const_eval_resolve( - mir_ty::ParamEnv::empty(), - unev, - Option::None, - ) { - std::result::Result::Ok(c) => { - // Evaluate the constant - // We need a param_env: we use the expression def id as a dummy id... - let (ty, val) = bt_ctx.translate_evaluated_operand_constant(&mir_ty, &c); - Option::Some( - bt_ctx - .t_ctx - .global_generate_assignment_body(ty, rust_id, val), - ) - } - std::result::Result::Err(e) => { - warn!("Did not evaluate {:?}: {:?}", rust_id, e); - Option::None - } + match bt_ctx.translate_body(rust_id.expect_local(), 0) { + Err(_) => { + // Error case: we could have a specific variant + None } + Ok(body) => Some(body), } + } else { + // Otherwise do nothing + None }; // Save the new global self.global_defs.insert( def_id, - ast::GlobalDecl { + GlobalDecl { def_id, meta, + is_local: rust_id.is_local(), name, - ty: g_ty, + ty, body, }, ); + + Ok(()) } } diff --git a/charon/src/translate_predicates.rs b/charon/src/translate_predicates.rs new file mode 100644 index 00000000..68d85408 --- /dev/null +++ b/charon/src/translate_predicates.rs @@ -0,0 +1,1125 @@ +use crate::common::*; +use crate::formatter::Formatter; +use crate::gast::*; +use crate::meta::Meta; +use crate::translate_ctx::*; +use crate::types::*; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; +use macros::{EnumAsGetters, EnumIsA, EnumToGetters}; +use rustc_hir::def_id::DefId; + +/// Same as [TraitClause], but where the clause id is a [TraitInstanceId]. +/// We need this information to solve the provenance of traits coming from +/// where clauses: when translating the where clauses and adding them to the +/// context, we recursively explore their parent/item clauses. +/// +/// We have to do this because of this kind of situations +///```text +/// trait Foo { +/// type W: Bar // Bar contains a method bar +/// } +/// +/// fn f(x : T::W) { +/// x.bar(); // We need to refer to the trait clause declared for Foo::W +/// // and this clause is not immediately accessible. +/// } +/// +/// trait FooChild : Foo {} +/// +/// fn g(x: ::W) { ... } +/// ^^^^^^^^^^^^^ +/// // We need to have access to a clause `FooChild : Foo` to solve this +/// ``` +#[derive(Debug, Clone)] +pub(crate) struct NonLocalTraitClause { + pub clause_id: TraitInstanceId, + /// [Some] if this is the top clause, [None] if this is about a parent/ + /// associated type clause. + pub meta: Option, + pub trait_id: TraitDeclId::Id, + pub generics: GenericArgs, +} + +impl NonLocalTraitClause { + pub(crate) fn to_local_trait_clause(&self) -> Option { + if let TraitInstanceId::Clause(id) = &self.clause_id { + Some(TraitClause { + clause_id: *id, + meta: self.meta, + trait_id: self.trait_id, + generics: self.generics.clone(), + }) + } else { + None + } + } + + pub(crate) fn to_trait_clause_with_id( + &self, + get_id: &dyn Fn(&TraitInstanceId) -> Option, + ) -> Option { + get_id(&self.clause_id).map(|clause_id| TraitClause { + clause_id, + meta: self.meta, + trait_id: self.trait_id, + generics: self.generics.clone(), + }) + } + + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let clause_id = self.clause_id.fmt_with_ctx(ctx); + let trait_id = ctx.format_object(self.trait_id); + let generics = self.generics.fmt_with_ctx(ctx); + format!("[{clause_id}]: {trait_id}{generics}") + } +} + +#[derive(Debug, Clone, EnumIsA, EnumAsGetters, EnumToGetters)] +pub(crate) enum Predicate { + Trait(NonLocalTraitClause), + TypeOutlives(TypeOutlives), + RegionOutlives(RegionOutlives), + TraitType(TraitTypeConstraint), +} + +impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn convert_params_info(info: hax::ParamsInfo) -> ParamsInfo { + ParamsInfo { + num_region_params: info.num_region_params, + num_type_params: info.num_type_params, + num_const_generic_params: info.num_const_generic_params, + num_trait_clauses: info.num_trait_clauses, + num_regions_outlive: info.num_regions_outlive, + num_types_outlive: info.num_types_outlive, + num_trait_type_constraints: info.num_trait_type_constraints, + } + } + + pub(crate) fn get_parent_params_info(&mut self, def_id: DefId) -> Option { + let params_info = + hax::get_parent_params_info(&self.hax_state, def_id).map(Self::convert_params_info); + + // Very annoying: because we may filter some marker traits (like [core::marker::Sized]) + // we have to recompute the number of trait clauses! + match params_info { + None => None, + Some(mut params_info) => { + let tcx = self.t_ctx.tcx; + let parent_id = tcx.generics_of(def_id).parent.unwrap(); + + let mut num_trait_clauses = 0; + // **IMPORTANT**: we do NOT want to use [TyCtxt::predicates_of]. + let preds = tcx.predicates_defined_on(parent_id).sinto(&self.hax_state); + for (pred, _) in preds.predicates { + if let hax::PredicateKind::Clause(hax::Clause::Trait(clause)) = &pred.value { + if self + .translate_trait_decl_id(clause.trait_ref.def_id.rust_def_id.unwrap()) + .is_some() + { + num_trait_clauses += 1; + } + } + } + params_info.num_trait_clauses = num_trait_clauses; + Some(params_info) + } + } + } + + pub(crate) fn get_predicates_of( + &mut self, + def_id: DefId, + ) -> Result { + // **IMPORTANT**: + // There are two functions which allow to retrieve the predicates of + // a definition: + // - [TyCtxt::predicates_defined_on]: returns exactly the list of predicates + // that the user has written on the definition: + // - [TyCtxt::predicates_of]: returns the user defined predicates and also: + // - if called on a trait `Foo`, we get an additional trait clause + // `Self : Foo` (i.e., the trait requires itself), which is not what we want. + // - for the type definitions, it also returns additional type/region outlives + // information, which the user doesn't have to write by hand (but it doesn't + // add those for functions). For instance, below: + // ``` + // type MutMut<'a, 'b> { + // x : &'a mut &'b mut u32, + // } + // ``` + // The rust compiler adds the predicate: `'b : 'a` ('b outlives 'a). + // For this reason we: + // - retrieve the trait predicates with [TyCtxt::predicates_defined_on] + // - retrieve the other predicates with [TyCtxt::predicates_of] + // + // Also, we reorder the predicates to make sure that the trait clauses come + // *before* the other clauses. This way we are sure that, when translating, + // all the trait clauses are in the context if we need them. + // + // Example: + // ``` + // f, U : Foo>(...) + // ^^^^ + // must make sure we have U : Foo in the context + // before translating this + // ``` + let tcx = self.t_ctx.tcx; + let param_env = tcx.param_env(def_id); + let parent: Option; + + let trait_preds = { + // Remark: we don't convert the predicates yet because we need to + // normalize them before. + let predicates = tcx.predicates_defined_on(def_id); + parent = predicates.parent.sinto(&self.hax_state); + let predicates: Vec<_> = predicates.predicates.iter().collect(); + trace!( + "TyCtxt::predicates_defined_on({:?}):\n{:?}", + def_id, + predicates + ); + + let trait_clauses: Vec<&(rustc_middle::ty::Predicate<'_>, rustc_span::Span)> = + predicates + .into_iter() + .filter(|x| { + matches!( + &x.0.kind().skip_binder(), + rustc_middle::ty::PredicateKind::Clause( + rustc_middle::ty::Clause::Trait(_) + ) + ) + }) + .collect(); + + let trait_clauses: Vec<(rustc_middle::ty::TraitPredicate<'_>, rustc_span::Span)> = + trait_clauses + .into_iter() + .map(|(pred, span)| { + if let Some(pred) = &pred.kind().no_bound_vars() { + if let rustc_middle::ty::PredicateKind::Clause( + rustc_middle::ty::Clause::Trait(tr), + ) = pred + { + // Normalize the trait clause + let tr = tcx.normalize_erasing_regions(param_env, *tr); + Ok((tr, *span)) + } else { + unreachable!(); + } + } else { + let err = Error { + span: *span, + msg: "Bound variables on predicate".to_string(), + }; + Err(err) + } + }) + .try_collect()?; + + let trait_preds: Vec<_> = trait_clauses + .iter() + .map(|(tr, span)| { + let value = + hax::PredicateKind::Clause(hax::Clause::Trait(tr.sinto(&self.hax_state))); + let pred = hax::Binder { + value, + bound_vars: Vec::new(), + }; + (pred, span.sinto(&self.hax_state)) + }) + .collect(); + trait_preds + }; + + let non_trait_preds = { + let predicates = tcx.predicates_of(def_id); + let predicates: Vec<_> = predicates.predicates.iter().collect(); + trace!("TyCtxt::predicates_of({:?}):\n{:?}", def_id, predicates); + + let non_trait_preds: Vec<&(rustc_middle::ty::Predicate<'_>, rustc_span::Span)> = + predicates + .into_iter() + .filter(|x| { + !(matches!( + &x.0.kind().skip_binder(), + rustc_middle::ty::PredicateKind::Clause( + rustc_middle::ty::Clause::Trait(_) + ) + )) + }) + .collect(); + trace!( + "TyCtxt::predicates_of({:?}) after filtering trait clauses:\n{:?}", + def_id, + non_trait_preds + ); + let non_trait_preds: Vec<_> = non_trait_preds + .iter() + .map(|(pred, span)| (pred.sinto(&self.hax_state), span.sinto(&self.hax_state))) + .collect(); + non_trait_preds + }; + + let predicates: Vec<(hax::Predicate, hax::Span)> = trait_preds + .into_iter() + .chain(non_trait_preds.into_iter()) + .collect(); + trace!("Predicates of {:?}\n{:?}", def_id, predicates); + Ok(hax::GenericPredicates { parent, predicates }) + } + + /// This function should be called **after** we translated the generics + /// (type parameters, regions...). + /// + /// [parent_predicates_as_parent_clauses]: if [Some], the predicates + /// of the parent must be registered as parent clauses. + pub(crate) fn translate_predicates_of( + &mut self, + parent_trait_id: Option, + def_id: DefId, + ) -> Result<(), Error> { + trace!("def_id: {:?}", def_id); + let tcx = self.t_ctx.tcx; + + // Get the predicates + // Note that we need to know *all* the predicates: we start + // with the parent. + match tcx.generics_of(def_id).parent { + None => { + trace!("No parents for {:?}", def_id); + } + Some(parent_id) => { + let preds = self.get_predicates_of(parent_id)?; + trace!("Predicates of parent ({:?}): {:?}", parent_id, preds); + + if let Some(trait_id) = parent_trait_id { + self.with_parent_trait_clauses( + TraitInstanceId::SelfId, + trait_id, + &mut |ctx: &mut Self| ctx.translate_predicates(&preds), + )?; + } else { + self.translate_predicates(&preds)?; + } + } + } + + let clauses = self + .trait_clauses + .values() + .map(|c| c.fmt_with_ctx(self)) + .collect::>() + .join(",\n"); + trace!( + "Local trait clauses of {:?} after translating the predicates of the parent:\n{}", + def_id, + clauses + ); + + // The predicates of the current definition + let preds = self.get_predicates_of(def_id)?; + trace!("Local predicates of {:?}:\n{:?}", def_id, preds); + self.translate_predicates(&preds)?; + + let clauses = self + .trait_clauses + .values() + .map(|c| c.fmt_with_ctx(self)) + .collect::>() + .join(",\n"); + trace!( + "All trait clauses of {:?} (parents + locals):\n{}", + def_id, + clauses + ); + Ok(()) + } + + /// Translate the predicates then solve the unsolved trait obligations + /// in the registered trait clauses. + pub(crate) fn translate_predicates_solve_trait_obligations_of( + &mut self, + parent_trait_id: Option, + def_id: DefId, + ) -> Result<(), Error> { + let span = self.t_ctx.tcx.def_span(def_id); + self.while_registering_trait_clauses(&mut |ctx| { + ctx.translate_predicates_of(parent_trait_id, def_id)?; + ctx.solve_trait_obligations_in_trait_clauses(span); + Ok(()) + }) + } + + pub(crate) fn translate_predicates( + &mut self, + preds: &hax::GenericPredicates, + ) -> Result<(), Error> { + self.translate_predicates_vec(&preds.predicates) + } + + pub(crate) fn translate_predicates_vec( + &mut self, + preds: &Vec<(hax::Predicate, hax::Span)>, + ) -> Result<(), Error> { + trace!("Predicates:\n{:?}", preds); + // We reorder the trait predicates so that we translate the predicates + // which introduce trait clauses *before* translating the other predicates + // (because in order to translate the latters we might need to solve + // trait parameters which need the formers). + use hax::{Clause, PredicateKind}; + let (preds_traits, preds): (Vec<_>, Vec<_>) = preds + .iter() + .partition(|(pred, _)| matches!(&pred.value, PredicateKind::Clause(Clause::Trait(_)))); + let preds: Vec<_> = preds_traits.into_iter().chain(preds.into_iter()).collect(); + + for (pred, span) in preds { + match self.translate_predicate(pred, span)? { + None => (), + Some(pred) => match pred { + Predicate::Trait(_) => { + // Don't need to do anything because the clause is already + // registered in [self.trait_clauses] + } + Predicate::TypeOutlives(p) => self.types_outlive.push(p), + Predicate::RegionOutlives(p) => self.regions_outlive.push(p), + Predicate::TraitType(p) => self.trait_type_constraints.push(p), + }, + } + } + Ok(()) + } + + /// Returns an [Option] because we may filter clauses about builtin or + /// auto traits like [core::marker::Sized] and [core::marker::Sync]. + /// + /// TODO: This function is used (among other things) to save trait clauses in the + /// context, so that we can use them when solving the trait obligations which depend + /// on the trait parameters. In order to make the resolution truly work, we should + /// (give the possibility of) normalizing the types. + pub(crate) fn translate_trait_clause( + &mut self, + hspan: &hax::Span, + trait_pred: &hax::TraitPredicate, + ) -> Result, Error> { + // Note sure what this is about + assert!(trait_pred.is_positive); + let span = hspan.rust_span; + + // We translate trait clauses for signatures, etc. so we do not erase the regions + let erase_regions = false; + + let trait_ref = &trait_pred.trait_ref; + let trait_id = self.translate_trait_decl_id(trait_ref.def_id.rust_def_id.unwrap()); + // We might have to ignore the trait + let trait_id = if let Some(trait_id) = trait_id { + trait_id + } else { + return Ok(None); + }; + + let (regions, types, const_generics) = + self.translate_substs(span, erase_regions, None, &trait_ref.generic_args)?; + // There are no trait refs + let generics = GenericArgs::new(regions, types, const_generics, Vec::new()); + + // Compute the current clause id + let clause_id = (self.trait_instance_id_gen)(); + let meta = self.translate_meta_from_rspan(hspan.clone()); + + // Immediately register the clause (we may need to refer to it in the parent/ + // item clauses) + let trait_clause = NonLocalTraitClause { + clause_id: clause_id.clone(), + meta: Some(meta), + trait_id, + generics, + }; + self.trait_clauses + .insert(trait_clause.clause_id.clone(), trait_clause.clone()); + + // [translate_trait_clause] takes care of registering the clause + let _parent_clauses: Vec<_> = + self.with_parent_trait_clauses(clause_id.clone(), trait_id, &mut |ctx: &mut Self| { + trait_pred + .parent_preds + .iter() + .map(|x| + // TODO: the span information is not correct + ctx.translate_trait_clause(hspan, x)) + .try_collect() + })?; + + // [translate_trait_clause] takes care of registering the clause + let _items_clauses: Vec<_> = trait_pred + .items_preds + .iter() + .map(|(name, clauses)| { + let clauses: Vec<_> = self.with_item_trait_clauses( + clause_id.clone(), + trait_id, + name.clone(), + &mut |ctx: &mut Self| { + clauses + .iter() + .map(|clause| { + // The clause is inside a binder + assert!(clause.bound_vars.is_empty()); + // TODO: the span is not correct + ctx.translate_trait_clause(hspan, &clause.value) + }) + .try_collect() + }, + )?; + Ok((TraitItemName(name.to_string()), clauses)) + }) + .try_collect()?; + + // Return + Ok(Some(trait_clause)) + } + + pub(crate) fn translate_predicate( + &mut self, + pred: &hax::Predicate, + hspan: &hax::Span, + ) -> Result, Error> { + trace!("{:?}", pred); + // Predicates are always used in signatures/type definitions, etc. + // For this reason, we do not erase the regions. + let erase_regions = false; + let span = hspan.rust_span; + + // Skip the binder (which lists the quantified variables). + // By doing so, we allow the predicates to contain DeBruijn indices, + // but it is ok because we only do a simple check. + let pred_kind = &pred.value; + use hax::{Clause, PredicateKind}; + match pred_kind { + PredicateKind::Clause(Clause::Trait(trait_pred)) => Ok(self + .translate_trait_clause(hspan, trait_pred)? + .map(Predicate::Trait)), + PredicateKind::Clause(Clause::RegionOutlives(p)) => { + let r0 = self.translate_region(span, erase_regions, &p.0)?; + let r1 = self.translate_region(span, erase_regions, &p.1)?; + Ok(Some(Predicate::RegionOutlives(OutlivesPred(r0, r1)))) + } + PredicateKind::Clause(Clause::TypeOutlives(p)) => { + let ty = self.translate_ty(span, erase_regions, &p.0)?; + let r = self.translate_region(span, erase_regions, &p.1)?; + Ok(Some(Predicate::TypeOutlives(OutlivesPred(ty, r)))) + } + PredicateKind::Clause(Clause::Projection(p)) => { + // This is used to express constraints over associated types. + // For instance: + // ``` + // T : Foo + // ^^^^^^^^^^ + // ``` + let hax::ProjectionPredicate { + impl_source, + substs, + type_name, + ty, + } = p; + + let trait_ref = + self.translate_trait_impl_source(span, erase_regions, impl_source)?; + // The trait ref should be Some(...): the marker traits (that + // we may filter) don't have associated types. + let trait_ref = trait_ref.unwrap(); + + let (regions, types, const_generics) = self + .translate_substs(span, erase_regions, None, substs) + .unwrap(); + let generics = GenericArgs { + regions, + types, + const_generics, + trait_refs: Vec::new(), + }; + let ty = self.translate_ty(span, erase_regions, ty).unwrap(); + let type_name = TraitItemName(type_name.clone()); + Ok(Some(Predicate::TraitType(TraitTypeConstraint { + trait_ref, + generics, + type_name, + ty, + }))) + } + PredicateKind::Clause(Clause::ConstArgHasType(..)) => { + // I don't really understand that one. Why don't they put + // the type information in the const generic parameters + // directly? For now we just ignore it. + Ok(None) + } + PredicateKind::WellFormed(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::ObjectSafe(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::ClosureKind(_, _, _) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::Subtype(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::Coerce(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::ConstEvaluatable(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::ConstEquate(_, _) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::TypeWellFormedFromEnv(_) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::Ambiguous => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + PredicateKind::AliasRelate(..) => error_or_panic!( + self, + span, + format!("Unsupported predicate: {:?}", pred_kind) + ), + } + } + + pub(crate) fn translate_trait_impl_sources( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + impl_sources: &[hax::ImplSource], + ) -> Result, Error> { + let res: Vec<_> = impl_sources + .iter() + .map(|x| self.translate_trait_impl_source(span, erase_regions, x)) + .try_collect()?; + Ok(res.into_iter().flatten().collect()) + } + + /// Returns an [Option] because we may ignore some builtin or auto traits + /// like [core::marker::Sized] or [core::marker::Sync]. + pub(crate) fn translate_trait_impl_source( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + impl_source: &hax::ImplSource, + ) -> Result, Error> { + let trait_decl_ref = { + let trait_ref = &impl_source.trait_ref; + let trait_id = self.translate_trait_decl_id(trait_ref.def_id.rust_def_id.unwrap()); + let trait_id = if let Some(trait_id) = trait_id { + trait_id + } else { + return Ok(None); + }; + + let parent_trait_refs = Vec::new(); + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + None, + &trait_ref.generic_args, + &parent_trait_refs, + )?; + TraitDeclRef { trait_id, generics } + }; + + match self.translate_trait_impl_source_aux( + span, + erase_regions, + impl_source, + trait_decl_ref.clone(), + ) { + Ok(res) => Ok(res), + Err(err) => { + if !self.t_ctx.continue_on_failure { + panic!("Error during trait resolution: {}", err.msg) + } else { + let msg = format!("Error during trait resolution: {}", &err.msg); + self.span_err(span, &msg); + let trait_id = TraitInstanceId::Unknown(err.msg); + Ok(Some(TraitRef { + trait_id, + generics: GenericArgs::empty(), + trait_decl_ref, + })) + } + } + } + } + + pub(crate) fn translate_trait_impl_source_aux( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + impl_source: &hax::ImplSource, + trait_decl_ref: TraitDeclRef, + ) -> Result, Error> { + // TODO: in the body of this function: + trace!("impl_source: {:?}", impl_source); + use hax::ImplSourceKind; + + let trait_ref = match &impl_source.kind { + ImplSourceKind::UserDefined(data) => { + let def_id = data.impl_def_id.rust_def_id.unwrap(); + let trait_id = self.translate_trait_impl_id(def_id); + // We already tested above whether the trait should be filtered + let trait_id = trait_id.unwrap(); + let trait_id = TraitInstanceId::TraitImpl(trait_id); + + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + None, + &data.substs, + &data.nested, + )?; + TraitRef { + trait_id, + generics, + trait_decl_ref, + } + } + ImplSourceKind::Param(trait_ref, trait_refs, constness) => { + // Explanations about constness: https://stackoverflow.com/questions/70441495/what-is-impl-const-in-rust + trace!( + "impl_source: param:\n- trait_ref: {:?}\n- trait_refs: {:?}\n- constness: {:?}", + trait_ref, + trait_refs, + constness + ); + assert!(trait_ref.bound_vars.is_empty()); + let trait_ref = &trait_ref.value; + + let def_id = trait_ref.def_id.rust_def_id.unwrap(); + // Remark: we already filtered the marker traits when translating + // the trait decl ref: the trait id should be Some(...). + let trait_id = self.translate_trait_decl_id(def_id).unwrap(); + + // Retrieve the arguments + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + None, + &trait_ref.generic_args, + trait_refs, + )?; + assert!(generics.trait_refs.is_empty()); + + // We need to find the trait clause which corresponds to + // this obligation. + let trait_id = self.find_trait_clause_for_param(trait_id, &generics); + + assert!(generics.trait_refs.is_empty()); + // Ignore the arguments: we forbid using universal quantifiers + // on the trait clauses for now. + TraitRef { + trait_id, + generics: GenericArgs::empty(), + trait_decl_ref, + } + } + ImplSourceKind::Object(_) => { + error_or_panic!(self, span, "Unsupported trait impl source kind: object") + } + ImplSourceKind::Builtin(trait_ref, traits) => { + assert!(trait_ref.bound_vars.is_empty()); + let trait_ref = &trait_ref.value; + let def_id = trait_ref.def_id.rust_def_id.unwrap(); + // Remark: we already filtered the marker traits when translating + // the trait decl ref: the trait id should be Some(...). + let trait_id = self.translate_trait_decl_id(def_id).unwrap(); + + let trait_id = TraitInstanceId::BuiltinOrAuto(trait_id); + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + None, + &trait_ref.generic_args, + traits, + )?; + TraitRef { + trait_id, + generics, + trait_decl_ref, + } + } + ImplSourceKind::AutoImpl(data) => { + let def_id = data.trait_def_id.rust_def_id.unwrap(); + // Remark: we already filtered the marker traits when translating + // the trait decl ref: the trait id should be Some(...). + let trait_id = self.translate_trait_decl_id(def_id).unwrap(); + let trait_id = TraitInstanceId::BuiltinOrAuto(trait_id); + + TraitRef { + trait_id, + generics: GenericArgs::empty(), + trait_decl_ref, + } + } + ImplSourceKind::FnPointer(data) => { + let ty = self.translate_ty(span, erase_regions, &data.fn_ty)?; + let trait_id = TraitInstanceId::FnPointer(Box::new(ty)); + let trait_refs = + self.translate_trait_impl_sources(span, erase_regions, &data.nested)?; + let generics = GenericArgs { + regions: vec![], + types: vec![], + const_generics: vec![], + trait_refs, + }; + TraitRef { + trait_id, + generics, + trait_decl_ref, + } + } + ImplSourceKind::Closure(_) => { + let error = "Closures are not supported yet".to_string(); + self.span_err(span, &error); + if !self.t_ctx.continue_on_failure { + panic!("{}", error) + } else { + let trait_id = TraitInstanceId::Unknown(error); + TraitRef { + trait_id, + generics: GenericArgs::empty(), + trait_decl_ref, + } + } + } + ImplSourceKind::TraitUpcasting(_) => unimplemented!(), + ImplSourceKind::Error(msg) | ImplSourceKind::Todo(msg) => { + let error = format!("Error during trait resolution: {}", msg); + self.span_err(span, &error); + if !self.t_ctx.continue_on_failure { + panic!("{}", error) + } else { + let trait_id = TraitInstanceId::Unknown(msg.clone()); + TraitRef { + trait_id, + generics: GenericArgs::empty(), + trait_decl_ref, + } + } + } + }; + Ok(Some(trait_ref)) + } + + fn match_trait_clauses( + &self, + trait_id: TraitDeclId::Id, + generics: &GenericArgs, + clause: &NonLocalTraitClause, + ) -> bool { + trace!("Matching trait clauses:\n- trait_id: {:?}\n- generics: {:?}\n- clause.trait_id: {:?}\n- clause.generics: {:?}", + self.format_object(trait_id), generics.fmt_with_ctx(self), + self.format_object(clause.trait_id), clause.generics.fmt_with_ctx(self) + ); + // Check if the clause is about the same trait + if clause.trait_id != trait_id { + trace!("Not the same trait id"); + false + } else { + // Ignoring the regions for now + let tgt_types = &generics.types; + let tgt_const_generics = &generics.const_generics; + + let src_types = &clause.generics.types; + let src_const_generics = &clause.generics.const_generics; + + // We simply check the equality between the arguments: + // there are no universally quantified variables to unify. + // TODO: normalize the trait clauses (we actually + // need to check equality **modulo** equality clauses) + // TODO: if we need to unify (later, when allowing universal + // quantification over clause parameters), use types_utils::TySubst. + let matched = src_types == tgt_types && src_const_generics == tgt_const_generics; + trace!("Match successful: {}", matched); + matched + } + } + + /// Find the trait instance fullfilling a trait obligation. + /// TODO: having to do this is very annoying. Isn't there a better way? + fn find_trait_clause_for_param( + &self, + trait_id: TraitDeclId::Id, + generics: &GenericArgs, + ) -> TraitInstanceId { + trace!( + "Inside context of: {:?}\nSpan: {:?}", + self.def_id, + self.t_ctx.tcx.def_ident_span(self.def_id) + ); + + // Simply explore the trait clauses + for trait_clause in self.trait_clauses.values() { + if self.match_trait_clauses(trait_id, generics, trait_clause) { + return trait_clause.clause_id.clone(); + } + } + + // Could not find a clause. + // Check if we are in the registration process, otherwise report an error. + // TODO: we might be registering a where clause. + if self.registering_trait_clauses { + TraitInstanceId::Unsolved(trait_id, generics.clone()) + } else { + let trait_ref = format!( + "{}{}", + self.format_object(trait_id), + generics.fmt_with_ctx(self) + ); + let clauses: Vec = self + .trait_clauses + .values() + .map(|x| x.fmt_with_ctx(self)) + .collect(); + + if !self.t_ctx.continue_on_failure { + let clauses = clauses.join("\n"); + unreachable!( + "Could not find a clause for parameter:\n- target param: {}\n- available clauses:\n{}\n- context: {:?}", + trait_ref, clauses, self.def_id + ); + } else { + // Return the UNKNOWN clause + log::warn!( + "Could not find a clause for parameter:\n- target param: {}\n- available clauses:\n{}\n- context: {:?}", + trait_ref, clauses.join("\n"), self.def_id + ); + TraitInstanceId::Unknown(format!( + "Could not find a clause for parameter: {} (available clauses: {}) (context: {:?})", + trait_ref, + clauses.join("; "), + self.def_id + )) + } + } + } +} + +struct TraitInstancesSolver<'a, 'tcx, 'ctx, 'ctx1> { + /// The number of unsolved trait instances. This allows us to check whether we reached + /// a fixed point or not (so that we don't enter infinite loops if we fail to solve + /// some instances). + pub unsolved_count: usize, + /// The unsolved clauses. + pub unsolved: Vec<(TraitDeclId::Id, GenericArgs)>, + /// For error messages + pub span: rustc_span::Span, + /// The current context + pub ctx: &'a mut BodyTransCtx<'tcx, 'ctx, 'ctx1>, +} + +impl<'a, 'tcx, 'ctx, 'ctx1> MutTypeVisitor for TraitInstancesSolver<'a, 'tcx, 'ctx, 'ctx1> { + /// If we find an unsolved trait instance id, attempt to solve it + fn visit_trait_instance_id(&mut self, id: &mut TraitInstanceId) { + if let TraitInstanceId::Unsolved(trait_id, generics) = id { + let solved_id = self.ctx.find_trait_clause_for_param(*trait_id, generics); + + if let TraitInstanceId::Unsolved(..) = solved_id { + // Failure: increment the unsolved count + self.unsolved_count += 1; + } else { + // Success: replace + *id = solved_id; + } + } else { + MutTypeVisitor::default_visit_trait_instance_id(self, id); + } + } +} + +impl<'a, 'tcx, 'ctx, 'ctx1> SharedTypeVisitor for TraitInstancesSolver<'a, 'tcx, 'ctx, 'ctx1> { + /// If we find an unsolved trait instance id, save it + fn visit_trait_instance_id(&mut self, id: &TraitInstanceId) { + if let TraitInstanceId::Unsolved(trait_id, generics) = id { + self.unsolved.push((*trait_id, generics.clone())) + } else { + SharedTypeVisitor::default_visit_trait_instance_id(self, id); + } + } +} + +impl<'a, 'tcx, 'ctx, 'ctx1> TraitInstancesSolver<'a, 'tcx, 'ctx, 'ctx1> { + /// Auxiliary function + fn visit(&mut self, solve: bool) { + // + // Explore the trait clauses map + // + + // For now we clone the trait clauses map - we will make it more effcicient later + let mut trait_clauses: Vec<(TraitInstanceId, NonLocalTraitClause)> = self + .ctx + .trait_clauses + .iter() + .map(|(id, c)| (id.clone(), c.clone())) + .collect(); + for (_, clause) in trait_clauses.iter_mut() { + if solve { + MutTypeVisitor::visit_trait_instance_id(self, &mut clause.clause_id); + MutTypeVisitor::visit_generic_args(self, &mut clause.generics); + } else { + SharedTypeVisitor::visit_trait_instance_id(self, &clause.clause_id); + SharedTypeVisitor::visit_generic_args(self, &clause.generics); + } + } + + // If we are solving: reconstruct the trait clauses map, and replace the one in the context + if solve { + self.ctx.trait_clauses = im::OrdMap::from(trait_clauses); + } + + // + // Also explore the other predicates + // Remark: we could do this *after* we solved all the trait obligations + // in the trait clauses map. + + // Types outlive predicates + // TODO: annoying that we have to clone + let mut types_outlive = self.ctx.types_outlive.clone(); + for x in &mut types_outlive { + if solve { + MutTypeVisitor::visit_type_outlives(self, x); + } else { + SharedTypeVisitor::visit_type_outlives(self, x); + } + } + // Replace + if solve { + self.ctx.types_outlive = types_outlive; + } + + // Trait type constraints predicates + // TODO: annoying that we have to clone + let mut trait_type_constraints = self.ctx.trait_type_constraints.clone(); + for x in &mut trait_type_constraints { + if solve { + MutTypeVisitor::visit_trait_type_constraint(self, x); + } else { + SharedTypeVisitor::visit_trait_type_constraint(self, x); + } + } + // Replace + if solve { + self.ctx.trait_type_constraints = trait_type_constraints; + } + } + + /// Perform one pass of solving the trait obligations + fn solve_one_pass(&mut self) { + self.unsolved_count = 0; + self.visit(true); + } + + fn collect_unsolved(&mut self) { + self.unsolved.clear(); + self.visit(false); + } + + /// While there are unsolved trait obligations in the registered trait + /// clauses, solve them (unless we get stuck). + pub(crate) fn solve_repeat(&mut self) { + self.solve_one_pass(); + + let mut count = self.unsolved_count; + let mut pass_id = 0; + while count > 0 { + log::trace!("Pass id: {}, unsolved count: {}", pass_id, count); + self.solve_one_pass(); + if self.unsolved_count >= count { + // We're stuck: report an error + + // Retraverse the context, collecting the unsolved clauses. + self.collect_unsolved(); + + let unsolved = self + .unsolved + .iter() + .map(|(trait_id, generics)| { + format!( + "{}{}", + self.ctx.format_object(*trait_id), + generics.fmt_with_ctx(&*self.ctx) + ) + }) + .collect::>() + .join("\n"); + let clauses = self + .ctx + .trait_clauses + .values() + .map(|x| x.fmt_with_ctx(&*self.ctx)) + .collect::>() + .join("\n"); + + if !self.ctx.t_ctx.continue_on_failure { + unreachable!( + "Could not find clauses for trait obligations:{}\n\nAvailable clauses:\n{}\n- context: {:?}", + unsolved, clauses, self.ctx.def_id + ); + } else { + let msg = format!("Could not find clauses for trait obligations:{}\n\nAvailable clauses:\n{}\n- context: {:?}", + unsolved, clauses, self.ctx.def_id); + self.ctx.span_err(self.span, &msg); + } + + return; + } else { + // We made progress: update the count + count = self.unsolved_count; + pass_id += 1; + } + } + + // We're done: check the where clauses which are not trait predicates + self.collect_unsolved(); + assert!(self.unsolved.is_empty()); + } +} + +impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { + /// Solve the unsolved trait obligations in the trait clauses (some clauses + /// may refer to other clauses, meaning that we are not necessarily able + /// to solve all the trait obligations when registering a clause, but might + /// be able later). + pub(crate) fn solve_trait_obligations_in_trait_clauses(&mut self, span: rustc_span::Span) { + let mut solver = TraitInstancesSolver { + unsolved_count: 0, + unsolved: Vec::new(), + span, + ctx: self, + }; + + solver.solve_repeat() + } +} diff --git a/charon/src/translate_traits.rs b/charon/src/translate_traits.rs new file mode 100644 index 00000000..fed71088 --- /dev/null +++ b/charon/src/translate_traits.rs @@ -0,0 +1,642 @@ +use crate::common::*; +use crate::gast::*; +use crate::translate_ctx::*; +use crate::types::*; +use crate::ullbc_ast as ast; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; +use rustc_hir::def_id::DefId; +use std::collections::HashMap; + +impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { + fn translate_ty_from_trait_item( + &mut self, + item: &rustc_middle::ty::AssocItem, + ) -> Result { + let erase_regions = false; + let tcx = self.t_ctx.tcx; + self.translate_ty( + tcx.def_span(item.def_id), + erase_regions, + &tcx.type_of(item.def_id) + .subst_identity() + .sinto(&self.hax_state), + ) + } + + /// Helper for [translate_trait_impl]. + /// + /// Remark: the [decl_item] is the item from the trait declaration. + fn translate_trait_refs_from_impl_trait_item( + &mut self, + trait_impl_def_id: DefId, + rust_impl_trait_ref: &rustc_middle::ty::TraitRef<'tcx>, + decl_item: &rustc_middle::ty::AssocItem, + ) -> Result, Error> { + trace!( + "- trait_impl_def_id: {:?}\n- rust_impl_trait_ref: {:?}\n- decl_item: {:?}", + trait_impl_def_id, + rust_impl_trait_ref, + decl_item + ); + + let tcx = self.t_ctx.tcx; + let span = tcx.def_span(trait_impl_def_id); + + // Lookup the trait clauses and substitute - TODO: not sure about the substitution + let subst = rust_impl_trait_ref.substs; + let bounds = tcx.item_bounds(decl_item.def_id); + let param_env = tcx.param_env(trait_impl_def_id); + let bounds = tcx.subst_and_normalize_erasing_regions(subst, param_env, bounds); + let erase_regions = false; + + // Solve the predicate bounds + let mut trait_refs = Vec::new(); + for bound in bounds { + if let rustc_middle::ty::PredicateKind::Clause(rustc_middle::ty::Clause::Trait( + trait_pred, + )) = bound.kind().skip_binder() + { + let trait_ref = rustc_middle::ty::Binder::dummy(trait_pred.trait_ref); + let trait_ref = hax::solve_trait(&self.hax_state, param_env, trait_ref); + let trait_ref = + self.translate_trait_impl_source(span, erase_regions, &trait_ref)?; + if let Some(trait_ref) = trait_ref { + trait_refs.push(trait_ref); + } + } + } + + // Return + Ok(trait_refs) + } + + fn translate_const_from_trait_item( + &mut self, + item: &rustc_middle::ty::AssocItem, + ) -> Result<(TraitItemName, (Ty, GlobalDeclId::Id)), Error> { + let ty = self.translate_ty_from_trait_item(item)?; + let name = TraitItemName(item.name.to_string()); + let id = self.translate_global_decl_id(item.def_id); + Ok((name, (ty, id))) + } + + /// Add the self trait clause, for itself (if it is a trait declaration) or + /// for its parent (if it is a trait item). + /// + /// We take a def id parameter because the clause may be retrieved from + /// an id which is not the id of the item under scrutinee (if the current + /// id is for an item trait, we need to lookup the trait itself and give + /// its id). + pub(crate) fn add_self_trait_clause(&mut self, def_id: DefId) -> Result<(), Error> { + trace!("id: {:?}", def_id); + // The self trait clause is actually the *last* trait predicate given by + // [TyCtxt::predicates_of]. + // **ATTENTION**: this doesn't return the same thing as [TyCtxt::predicates_defined_on], + // which we use elsewhere. + + // Sanity check: we should add the self trait clause before we start + // translating the clauses. + assert!(self.trait_clauses.is_empty()); + + let predicates = self.t_ctx.tcx.predicates_of(def_id); + trace!("predicates: {:?}", predicates); + + // Get the last predicate + let (pred, span) = predicates.predicates.iter().next_back().unwrap(); + let pred = pred.sinto(&self.hax_state); + let span = span.sinto(&self.hax_state); + + // Convert to a clause + assert!(pred.bound_vars.is_empty()); + let self_pred = + if let hax::PredicateKind::Clause(hax::Clause::Trait(trait_pred)) = pred.value { + if self + .translate_trait_decl_id(trait_pred.trait_ref.def_id.rust_def_id.unwrap()) + .is_some() + { + trait_pred + } else { + panic!(); + } + } else { + panic!(); + }; + + // Convert + let mut initialized = false; + let self_instance_id_gen = Box::new(move || { + assert!(!initialized); + initialized = true; + TraitInstanceId::SelfId + }); + let self_clause = self.with_local_trait_clauses(self_instance_id_gen, &mut |s| { + s.translate_trait_clause(&span, &self_pred) + })?; + trace!("self clause: {}", self_clause.unwrap().fmt_with_ctx(self)); + Ok(()) + } + + /// Similar to [add_self_trait_clause] but for trait implementations. + /// + /// We call this when translating trait impls and trait impl items. + pub(crate) fn add_trait_impl_self_trait_clause( + &mut self, + impl_id: TraitImplId::Id, + ) -> Result<(), Error> { + let def_id = *self.t_ctx.trait_impl_id_to_def_id.get(&impl_id).unwrap(); + trace!("id: {:?}", def_id); + + // Retrieve the trait ref representing "self" + let tcx = self.t_ctx.tcx; + let rustc_middle::ty::ImplSubject::Trait(trait_ref) = + tcx.impl_subject(def_id).subst_identity() else { unreachable!() }; + + // Wrap it in a [TraitPredicate] so that when calling [sinto] we retrieve + // the parent and item predicates. + let trait_pred = rustc_middle::ty::TraitPredicate { + trait_ref, + // Not really necessary (dummy value) + constness: rustc_middle::ty::BoundConstness::NotConst, + // Not really necessary + polarity: rustc_middle::ty::ImplPolarity::Positive, + }; + let trait_pred = trait_pred.sinto(&self.hax_state); + + // Save the self clause (and its parent/item clauses) + let mut initialized = false; + let span = tcx.def_span(def_id).sinto(&self.t_ctx.hax_state); + let _ = self.with_local_trait_clauses( + Box::new(move || { + assert!(!initialized); + initialized = true; + TraitInstanceId::SelfId + }), + &mut |s| s.translate_trait_clause(&span, &trait_pred), + )?; + Ok(()) + } + + pub(crate) fn with_parent_trait_clauses( + &mut self, + clause_id: TraitInstanceId, + trait_decl_id: TraitDeclId::Id, + f: &mut dyn FnMut(&mut Self) -> T, + ) -> T { + let mut parent_clause_id_gen = TraitClauseId::Generator::new(); + let parent_trait_instance_id_gen = Box::new(move || { + let fresh_id = parent_clause_id_gen.fresh_id(); + TraitInstanceId::ParentClause(Box::new(clause_id.clone()), trait_decl_id, fresh_id) + }); + + self.with_local_trait_clauses(parent_trait_instance_id_gen, f) + } + + pub(crate) fn with_item_trait_clauses( + &mut self, + clause_id: TraitInstanceId, + trait_decl_id: TraitDeclId::Id, + item_name: String, + f: &mut dyn FnMut(&mut Self) -> T, + ) -> T { + let mut item_clause_id_gen = TraitClauseId::Generator::new(); + let item_clause_id_gen = Box::new(move || { + let fresh_id = item_clause_id_gen.fresh_id(); + TraitInstanceId::ItemClause( + Box::new(clause_id.clone()), + trait_decl_id, + TraitItemName(item_name.clone()), + fresh_id, + ) + }); + + self.with_local_trait_clauses(item_clause_id_gen, f) + } +} + +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { + /// Remark: this **doesn't** register the def id (on purpose) + pub(crate) fn translate_trait_item_name(&mut self, rust_id: DefId) -> TraitItemName { + // Translate the name + let name = self.item_def_id_to_name(rust_id); + let (name, id) = name.name.last().unwrap().as_ident(); + assert!(id.is_zero()); + TraitItemName(name.to_string()) + } + + pub(crate) fn translate_trait_decl(&mut self, rust_id: DefId) { + // TODO: for now, if there is an error while translating the parameters/ + // predicates of the declaration, we ignore it altogether, while we should + // save somewhere that we failed to extract it. + if self.translate_trait_decl_aux(rust_id).is_err() { + // TODO + } + } + + /// Auxliary helper to properly handle errors, see [translate_trait_decl]. + fn translate_trait_decl_aux(&mut self, rust_id: DefId) -> Result<(), Error> { + trace!("About to translate trait decl:\n{:?}", rust_id); + + let def_id = self.translate_trait_decl_id(rust_id); + // We may need to ignore the trait (happens if the trait is a marker + // trait like [core::marker::Sized] + if def_id.is_none() { + return Ok(()); + } + let def_id = def_id.unwrap(); + + trace!("Trait decl id:\n{:?}", def_id); + + let mut bt_ctx = BodyTransCtx::new(rust_id, self); + + let name = bt_ctx + .t_ctx + .extended_def_id_to_name(&rust_id.sinto(&bt_ctx.hax_state)); + + // Translate the generic + bt_ctx.translate_generic_params(rust_id)?; + + // Add the trait clauses + bt_ctx.while_registering_trait_clauses(&mut |bt_ctx| { + // Add the self trait clause + bt_ctx.add_self_trait_clause(rust_id)?; + + // Translate the predicates. + bt_ctx.with_parent_trait_clauses(TraitInstanceId::SelfId, def_id, &mut |s| { + s.translate_predicates_of(None, rust_id) + }) + })?; + + // TODO: move this below (we don't need to perform this function call exactly here) + let preds = bt_ctx.get_predicates(); + + // Explore the associated items + // We do something subtle here: TODO: explain + let tcx = bt_ctx.t_ctx.tcx; + let mut consts = Vec::new(); + let mut types = Vec::new(); + let mut required_methods = Vec::new(); + let mut provided_methods = Vec::new(); + for item in tcx.associated_items(rust_id).in_definition_order() { + use rustc_middle::ty::AssocKind; + + let has_default_value = item.defaultness(tcx).has_value(); + match &item.kind { + AssocKind::Fn => { + let method_name = bt_ctx.t_ctx.translate_trait_item_name(item.def_id); + // Skip the provided methods for the *external* trait declarations, + // but still remember their name. + if has_default_value { + // This is a *provided* method + if rust_id.is_local() { + let fun_id = bt_ctx.translate_fun_decl_id(item.def_id); + provided_methods.push((method_name, Some(fun_id))); + } else { + provided_methods.push((method_name, None)); + } + } else { + // This is a required method (no default implementation) + let fun_id = bt_ctx.translate_fun_decl_id(item.def_id); + required_methods.push((method_name, fun_id)); + } + } + AssocKind::Const => { + // Check if the constant has a value (i.e., a body). + // We are handling a trait *declaration* so we need to + // check whether the constant has a default value. + trace!("id: {:?}\n- item: {:?}", rust_id, item); + let c = if has_default_value { + let (name, (ty, id)) = bt_ctx.translate_const_from_trait_item(item)?; + (name, (ty, Some(id))) + } else { + let ty = bt_ctx.translate_ty_from_trait_item(item)?; + let name = TraitItemName(item.name.to_string()); + (name, (ty, None)) + }; + consts.push(c); + } + AssocKind::Type => { + let name = item.name.to_string(); + + // Translating the predicates + { + // TODO: this is an ugly manip + let bounds = tcx.item_bounds(item.def_id).subst_identity(); + use crate::rustc_middle::query::Key; + let span = bounds.default_span(tcx); + let bounds: Vec<_> = bounds.into_iter().map(|x| (x, span)).collect(); + let bounds = bounds.sinto(&bt_ctx.hax_state); + + // Register the trait clauses as item trait clauses + bt_ctx.with_item_trait_clauses( + TraitInstanceId::SelfId, + def_id, + name.clone(), + &mut |s| s.translate_predicates_vec(&bounds), + )?; + } + + // Retrieve the trait clauses which are specific to this item + // - we simply need to filter the trait clauses by using their id. + let item_trait_clauses: Vec<_> = bt_ctx + .trait_clauses + .values() + .filter_map(|c| { + c.to_trait_clause_with_id(&|id| match id { + TraitInstanceId::ItemClause( + box TraitInstanceId::SelfId, + _, + TraitItemName(item_name), + clause_id, + ) => { + if item_name == &name { + Some(*clause_id) + } else { + None + } + } + _ => None, + }) + }) + .collect(); + + let ty = if has_default_value { + Some(bt_ctx.translate_ty_from_trait_item(item)?) + } else { + None + }; + + types.push((TraitItemName(name), (item_trait_clauses, ty))); + } + } + } + + // Note that in the generics returned by [get_generics], the trait refs + // only contain the local trait clauses. + let generics = bt_ctx.get_generics(); + // TODO: maybe we should do something about the predicates? + + let parent_clauses = bt_ctx.get_parent_trait_clauses(); + + // Debugging: + { + let clauses = bt_ctx + .trait_clauses + .values() + .map(|c| c.fmt_with_ctx(&bt_ctx)) + .collect::>() + .join("\n"); + let generic_clauses = generics + .trait_clauses + .iter() + .map(|c| c.fmt_with_ctx(&bt_ctx)) + .collect::>() + .join("\n"); + trace!( + "Trait decl: {:?}:\n- all clauses:\n{}\n- generic.trait_clauses:\n{}\n", + def_id, + clauses, + generic_clauses + ); + } + + // In case of a trait implementation, some values may not have been + // provided, in case the declaration provided default values. We + // check those, and lookup the relevant values. + let trait_decl = ast::TraitDecl { + def_id, + is_local: rust_id.is_local(), + name, + meta: self.translate_meta_from_rid(rust_id), + generics, + preds, + parent_clauses, + consts, + types, + required_methods, + provided_methods, + }; + self.trait_decls.insert(def_id, trait_decl); + + Ok(()) + } + + pub(crate) fn translate_trait_impl(&mut self, rust_id: DefId) { + // TODO: for now, if there is an error while translating the parameters/ + // predicates of the declaration, we ignore it altogether, while we should + // save somewhere that we failed to extract it. + if self.translate_trait_impl_aux(rust_id).is_err() { + // TODO + } + } + + /// Auxliary helper to properly handle errors, see [translate_impl_decl]. + fn translate_trait_impl_aux(&mut self, rust_id: DefId) -> Result<(), Error> { + trace!("About to translate trait impl:\n{:?}", rust_id); + + let def_id = self.translate_trait_impl_id(rust_id); + // We may need to ignore the trait + if def_id.is_none() { + return Ok(()); + } + let def_id = def_id.unwrap(); + trace!("Trait impl id:\n{:?}", def_id); + + let tcx = self.tcx; + let span = tcx.def_span(rust_id); + let mut bt_ctx = BodyTransCtx::new(rust_id, self); + + let name = bt_ctx + .t_ctx + .extended_def_id_to_name(&rust_id.sinto(&bt_ctx.hax_state)); + let erase_regions = false; + + // Translate the generics + bt_ctx.translate_generic_params(rust_id)?; + + // Add the trait self clauses + bt_ctx.while_registering_trait_clauses(&mut |bt_ctx| { + // Translate the predicates + bt_ctx.translate_predicates_of(None, rust_id)?; + + // Add the self trait clause + bt_ctx.add_trait_impl_self_trait_clause(def_id)?; + + // + bt_ctx.solve_trait_obligations_in_trait_clauses(span); + + Ok(()) + })?; + + // Retrieve the information about the implemented trait. + let ( + implemented_trait_rust_id, + implemented_trait, + rust_implemented_trait_ref, + // [parent_trait_refs]: the trait refs which implement the parent + // clauses of the implemented trait decl. + parent_trait_refs, + ) = { + // TODO: what is below duplicates a bit [add_trait_impl_self_trait_clause] + let trait_rust_id = tcx.trait_id_of_impl(rust_id).unwrap(); + let trait_id = bt_ctx.translate_trait_decl_id(trait_rust_id); + // We already tested above whether the trait should be filtered + let trait_id = trait_id.unwrap(); + + let rustc_middle::ty::ImplSubject::Trait(rust_trait_ref) = + tcx.impl_subject(rust_id).subst_identity() else { unreachable!() }; + let trait_ref = rust_trait_ref.sinto(&bt_ctx.hax_state); + let (regions, types, const_generics) = + bt_ctx.translate_substs(span, erase_regions, None, &trait_ref.generic_args)?; + + let parent_trait_refs = hax::solve_item_traits( + &bt_ctx.hax_state, + tcx.param_env(rust_id), + rust_trait_ref.def_id, + rust_trait_ref.substs, + ); + let parent_trait_refs: Vec = + bt_ctx.translate_trait_impl_sources(span, erase_regions, &parent_trait_refs)?; + let parent_trait_refs: TraitClauseId::Vector = + TraitClauseId::Vector::from(parent_trait_refs); + + let generics = GenericArgs { + regions, + types, + const_generics, + trait_refs: Vec::new(), + }; + let trait_ref = TraitDeclRef { trait_id, generics }; + (trait_rust_id, trait_ref, rust_trait_ref, parent_trait_refs) + }; + + { + // Debugging + let refs = parent_trait_refs + .iter() + .map(|c| c.fmt_with_ctx(&bt_ctx)) + .collect::>() + .join("\n"); + trace!("Trait impl: {:?}\n- parent_trait_refs:\n{}", rust_id, refs); + } + + // Explore the trait decl method items to retrieve the list of required methods + use std::collections::HashSet; + let mut decl_required_methods: HashSet = HashSet::new(); + for item in tcx + .associated_items(implemented_trait_rust_id) + .in_definition_order() + { + if let AssocKind::Fn = &item.kind && !item.defaultness(tcx).has_value() { + decl_required_methods.insert(item.name.to_string()); + } + } + + // Explore the clauses of the + + // Explore the associated items + // We do something subtle here: TODO + let tcx = bt_ctx.t_ctx.tcx; + let mut consts = HashMap::new(); + let mut types: HashMap = HashMap::new(); + let mut required_methods = Vec::new(); + let mut provided_methods = Vec::new(); + + use rustc_middle::ty::AssocKind; + for item in tcx.associated_items(rust_id).in_definition_order() { + match &item.kind { + AssocKind::Fn => { + let method_name = bt_ctx.t_ctx.translate_trait_item_name(item.def_id); + let fun_id = bt_ctx.translate_fun_decl_id(item.def_id); + + // Check if we implement a required method or reimplement + // a provided method + let is_required = decl_required_methods.contains(&method_name.0); + if is_required { + required_methods.push((method_name, fun_id)); + } else { + provided_methods.push((method_name, fun_id)); + } + } + AssocKind::Const => { + let (name, c) = bt_ctx.translate_const_from_trait_item(item)?; + consts.insert(name, c); + } + AssocKind::Type => { + let name = TraitItemName(item.name.to_string()); + let ty = bt_ctx.translate_ty_from_trait_item(item)?; + types.insert(name, ty); + } + } + } + + // In case of a trait implementation, some values may not have been + // provided, in case the declaration provided default values. We + // check those, and lookup the relevant values. + let partial_consts = consts; + let partial_types = types; + let mut consts = Vec::new(); + let mut types: Vec<(TraitItemName, (Vec, Ty))> = Vec::new(); + for item in tcx + .associated_items(implemented_trait_rust_id) + .in_definition_order() + { + match &item.kind { + AssocKind::Fn => (), + AssocKind::Const => { + let name = TraitItemName(item.name.to_string()); + // Does the trait impl provide an implementation for this const? + let c = match partial_consts.get(&name) { + Some(c) => c.clone(), + None => { + // The item is not defined in the trait impl: + // the trait decl *must* define a default value. + bt_ctx.translate_const_from_trait_item(item)?.1 + } + }; + consts.push((name, c)); + } + AssocKind::Type => { + let name = TraitItemName(item.name.to_string()); + // Does the trait impl provide an implementation for this type? + let ty = match partial_types.get(&name) { + Some(ty) => ty.clone(), + None => { + // The item is not defined in the trait impl: + // the trait decl *must* define a default value. + // TODO: should we normalize the type? + bt_ctx.translate_ty_from_trait_item(item)? + } + }; + + // Retrieve the trait refs + let trait_refs = bt_ctx.translate_trait_refs_from_impl_trait_item( + rust_id, + &rust_implemented_trait_ref, + item, + )?; + + types.push((name, (trait_refs, ty))); + } + } + } + + let trait_impl = ast::TraitImpl { + def_id, + is_local: rust_id.is_local(), + name, + meta: bt_ctx.t_ctx.translate_meta_from_rid(rust_id), + impl_trait: implemented_trait, + generics: bt_ctx.get_generics(), + preds: bt_ctx.get_predicates(), + parent_trait_refs, + consts, + types, + required_methods, + provided_methods, + }; + self.trait_impls.insert(def_id, trait_impl); + + Ok(()) + } +} diff --git a/charon/src/translate_types.rs b/charon/src/translate_types.rs index 49f6a2c1..b56bd82b 100644 --- a/charon/src/translate_types.rs +++ b/charon/src/translate_types.rs @@ -1,36 +1,15 @@ use crate::assumed; use crate::common::*; -use crate::generics; -use crate::names::type_def_id_to_name; -use crate::regions_hierarchy::RegionGroups; +use crate::gast::*; use crate::translate_ctx::*; -use crate::types as ty; -use crate::types::ConstGeneric; +use crate::types::*; use core::convert::*; +use hax_frontend_exporter as hax; +use hax_frontend_exporter::SInto; use rustc_hir::def_id::DefId; -use rustc_middle::mir::Mutability; -use rustc_middle::ty::{Ty, TyKind}; -pub fn translate_region_name(region: &rustc_middle::ty::RegionKind<'_>) -> Option { - // Compute the region name - let s = match region { - rustc_middle::ty::RegionKind::ReEarlyBound(r) => Some(r.name.to_ident_string()), - rustc_middle::ty::RegionKind::ReLateBound(_, br) => match br.kind { - rustc_middle::ty::BoundRegionKind::BrAnon(..) => None, - rustc_middle::ty::BoundRegionKind::BrNamed(_, symbol) => Some(symbol.to_ident_string()), - rustc_middle::ty::BoundRegionKind::BrEnv => Some("@env".to_owned()), - }, - rustc_middle::ty::RegionKind::ReFree(r) => match r.bound_region { - rustc_middle::ty::BoundRegionKind::BrAnon(..) => None, - rustc_middle::ty::BoundRegionKind::BrNamed(_, symbol) => Some(symbol.to_ident_string()), - rustc_middle::ty::BoundRegionKind::BrEnv => Some("@env".to_owned()), - }, - _ => { - unreachable!(); - } - }; - - // We ignore the name when it is equal to "'_" +/// Small helper: we ignore some region names (when they are equal to "'_") +fn check_region_name(s: Option) -> Option { if s.is_some() && s.as_ref().unwrap() == "'_" { None } else { @@ -38,34 +17,135 @@ pub fn translate_region_name(region: &rustc_middle::ty::RegionKind<'_>) -> Optio } } -pub fn translate_non_erased_region<'tcx>( - region_params: &im::OrdMap, ty::RegionVarId::Id>, - region: rustc_middle::ty::RegionKind<'tcx>, -) -> ty::Region { - match region { - rustc_middle::ty::RegionKind::ReErased => unreachable!(), - rustc_middle::ty::RegionKind::ReStatic => ty::Region::Static, - _ => { - let rid = region_params.get(®ion).unwrap(); - ty::Region::Var(*rid) - } - } +pub fn translate_bound_region_kind_name(kind: &hax::BoundRegionKind) -> Option { + use hax::BoundRegionKind::*; + let s = match kind { + BrAnon(..) => None, + BrNamed(_, symbol) => Some(symbol.clone()), + BrEnv => Some("@env".to_owned()), + }; + check_region_name(s) } -/// Translate a region which is expected to be erased. -/// -/// The regions are expected to be erased inside the function bodies (i.e.: -/// we believe MIR uses regions only in the function signatures). -pub fn translate_erased_region(region: rustc_middle::ty::RegionKind<'_>) -> ty::ErasedRegion { - match region { - rustc_middle::ty::RegionKind::ReErased => ty::ErasedRegion::Erased, +pub fn translate_region_name(region: &hax::Region) -> Option { + // Compute the region name + use hax::{BoundRegionKind::*, RegionKind::*}; + let s = match ®ion.kind { + ReEarlyBound(r) => Some(r.name.clone()), + ReLateBound(_, br) => translate_bound_region_kind_name(&br.kind), + ReFree(r) => match &r.bound_region { + BrAnon(..) => None, + BrNamed(_, symbol) => Some(symbol.clone()), + BrEnv => Some("@env".to_owned()), + }, _ => { unreachable!(); } - } + }; + + // We check twice in the case of late bound regions, but it is ok... + check_region_name(s) } impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { + // Translate a region + pub(crate) fn translate_region( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + region: &hax::Region, + ) -> Result { + if erase_regions { + Ok(Region::Erased) + } else { + match ®ion.kind { + hax::RegionKind::ReErased => Ok(Region::Erased), + hax::RegionKind::ReStatic => Ok(Region::Static), + hax::RegionKind::ReLateBound(id, br) => { + // See the comments in [BodyTransCtx.bound_vars]: + // - the De Bruijn index identifies the group of variables + // - the var id identifies the variable inside the group + let rid = self + .bound_region_vars + .get(*id) + .unwrap() + .get(br.var) + .unwrap(); + Ok(Region::Var(*rid)) + } + hax::RegionKind::ReVar(re_var) => { + // TODO: I'm really not sure how to handle those, here. + // They sometimes appear and seem to refer to the early bound + // regions. But on the other hand, whenever I investigated, I + // only encountered those in *trait references* in trait + // implementations. + // + // For instance, here is a minimal example which triggers + // this case: + // ``` + // pub trait From { + // type Error; + // fn from(v: T) -> Result + // // ^^^^^^^^^^ + // // This associated type is important + // where + // Self: std::marker::Sized; + // + // impl From<&bool> for bool { + // // ^^^^^ + // // This reference is important + // type Error = (); + // + // fn from(v: &bool) -> Result { + // Ok(*v) + // } + // } + // ``` + // + // If we extract this to LLBC, wet get (focusing on the implementation + // of `from`): + // ``` + // ... // omitted + // + // fn crate::{bool}::from<@R0, @R1>(@1: &@R1 (bool)) -> + // core::result::Result::Error> { + // // ^^^ + // // The problematic region + // ... // omitted + // } + // ``` + error_assert!(self, span, self.region_vars_map.get(region).is_none()); + + for (rk, rid) in self.region_vars_map.map.iter() { + if let hax::RegionKind::ReEarlyBound(eb) = &rk.kind { + if eb.index == re_var.index { + return Ok(Region::Var(*rid)); + } + } + } + let err = format!( + "Could not find region: {:?}\n\nRegion vars map:\n{:?}\n\nBound region vars:\n{:?}", + region, self.region_vars_map, self.bound_region_vars + ); + error_or_panic!(self, span, err) + } + _ => { + // For the other regions, we use the regions map + match self.region_vars_map.get(region) { + Some(rid) => Ok(Region::Var(rid)), + None => { + let err = format!( + "Could not find region: {:?}\n\nRegion vars map:\n{:?}\n\nBound region vars:\n{:?}", + region, self.region_vars_map, self.bound_region_vars + ); + error_or_panic!(self, span, err) + } + } + } + } + } + } + /// Translate a Ty. /// /// Typically used in this module to translate the fields of a structure/ @@ -74,135 +154,146 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { /// Note that we take as parameter a function to translate regions, because /// regions can be translated in several manners (non-erased region or erased /// regions), in which case the return type is different. - pub(crate) fn translate_ty( + pub(crate) fn translate_ty( &mut self, - region_translator: &dyn Fn(&rustc_middle::ty::RegionKind<'tcx>) -> R, - ty: &Ty<'tcx>, - ) -> Result> - where - R: Clone + Eq, - { - self.translate_ty_kind(region_translator, ty.kind()) - } - - /// Translate a [TyKind]. - /// - /// See the comments for [translate_ty] (the two functions do the same thing, - /// they simply don't take the same input parameters). - pub(crate) fn translate_ty_kind( - &mut self, - region_translator: &dyn Fn(&rustc_middle::ty::RegionKind<'tcx>) -> R, - ty_kind: &TyKind<'tcx>, - ) -> Result> - where - R: Clone + Eq, - { - trace!("{:?}", ty_kind); - match ty_kind { - TyKind::Bool => Ok(ty::Ty::Literal(ty::LiteralTy::Bool)), - TyKind::Char => Ok(ty::Ty::Literal(ty::LiteralTy::Char)), - TyKind::Int(int_ty) => Ok(ty::Ty::Literal(ty::LiteralTy::Integer( - ty::IntegerTy::rust_int_ty_to_integer_ty(*int_ty), + span: rustc_span::Span, + erase_regions: bool, + ty: &hax::Ty, + ) -> Result { + trace!("{:?}", ty); + match ty { + hax::Ty::Bool => Ok(Ty::Literal(LiteralTy::Bool)), + hax::Ty::Char => Ok(Ty::Literal(LiteralTy::Char)), + hax::Ty::Int(int_ty) => Ok(Ty::Literal(LiteralTy::Integer( + IntegerTy::rust_int_ty_to_integer_ty(*int_ty), ))), - TyKind::Uint(int_ty) => Ok(ty::Ty::Literal(ty::LiteralTy::Integer( - ty::IntegerTy::rust_uint_ty_to_integer_ty(*int_ty), + hax::Ty::Uint(int_ty) => Ok(Ty::Literal(LiteralTy::Integer( + IntegerTy::rust_uint_ty_to_integer_ty(*int_ty), ))), - TyKind::Float(_) => { + hax::Ty::Float(_) => { trace!("Float"); - unimplemented!(); - } - TyKind::Never => Ok(ty::Ty::Never), - - TyKind::Alias(_, _) => { - unimplemented!(); + error_or_panic!(self, span, "Floats are not supported yet") } - - TyKind::Adt(adt, substs) => { - let adt_did = adt.did(); + hax::Ty::Never => Ok(Ty::Never), + + hax::Ty::Alias(alias_kind) => match alias_kind { + hax::AliasKind::Projection { + impl_source, + substs, + name, + } => { + let trait_ref = + self.translate_trait_impl_source(span, erase_regions, impl_source)?; + // This should succeed because no marker trait (that we may + // ignore) has associated types. + let trait_ref = trait_ref.unwrap(); + let (regions, types, const_generics) = + self.translate_substs(span, erase_regions, None, substs)?; + let generics = GenericArgs { + regions, + types, + const_generics, + trait_refs: Vec::new(), + }; + let name = TraitItemName(name.clone()); + Ok(Ty::TraitType(trait_ref, generics, name)) + } + _ => { + error_or_panic!(self, span, format!("Unimplemented: {:?}", ty)) + } + }, + + hax::Ty::Adt { + generic_args: substs, + trait_refs, + def_id, + } => { + let adt_did = def_id.rust_def_id.unwrap(); trace!("Adt: {:?}", adt_did); // Retrieve the list of used arguments let used_params = if adt_did.is_local() { Option::None } else { - let name = type_def_id_to_name(self.t_ctx.tcx, adt_did); + let name = self.t_ctx.def_id_to_name(def_id); assumed::type_to_used_params(&name) }; // Translate the type parameters instantiation - let (regions, params, cgs) = - self.translate_substs(region_translator, used_params, substs)?; + let generics = self.translate_substs_and_trait_refs( + span, + erase_regions, + used_params, + substs, + trait_refs, + )?; // Retrieve the ADT identifier - let def_id = self.translate_type_id(adt_did); + let def_id = self.translate_type_id(def_id); // Return the instantiated ADT - Ok(ty::Ty::Adt(def_id, regions, params, cgs)) + Ok(Ty::Adt(def_id, generics)) } - TyKind::Str => { + hax::Ty::Str => { trace!("Str"); - let id = ty::TypeId::Assumed(ty::AssumedTy::Str); - Ok(ty::Ty::Adt(id, Vec::new(), Vec::new(), Vec::new())) + let id = TypeId::Assumed(AssumedTy::Str); + Ok(Ty::Adt(id, GenericArgs::empty())) } - TyKind::Array(ty, const_param) => { + hax::Ty::Array(ty, const_param) => { trace!("Array"); - let c = self.translate_const_kind_as_const_generic(*const_param); - let tys = vec![self.translate_ty(region_translator, ty)?]; + let c = self.translate_constant_expr_to_const_generic(span, const_param)?; + let tys = vec![self.translate_ty(span, erase_regions, ty)?]; let cgs = vec![c]; - let id = ty::TypeId::Assumed(ty::AssumedTy::Array); - Ok(ty::Ty::Adt(id, Vec::new(), tys, cgs)) + let id = TypeId::Assumed(AssumedTy::Array); + Ok(Ty::Adt( + id, + GenericArgs::new(Vec::new(), tys, cgs, Vec::new()), + )) } - TyKind::Slice(ty) => { + hax::Ty::Slice(ty) => { trace!("Slice"); - let tys = vec![self.translate_ty(region_translator, ty)?]; - let id = ty::TypeId::Assumed(ty::AssumedTy::Slice); - Ok(ty::Ty::Adt(id, Vec::new(), tys, Vec::new())) + let tys = vec![self.translate_ty(span, erase_regions, ty)?]; + let id = TypeId::Assumed(AssumedTy::Slice); + Ok(Ty::Adt(id, GenericArgs::new_from_types(tys))) } - TyKind::Ref(region, ty, mutability) => { + hax::Ty::Ref(region, ty, mutability) => { trace!("Ref"); - let region = region_translator(region); - let ty = self.translate_ty(region_translator, ty)?; - let kind = match *mutability { - Mutability::Not => ty::RefKind::Shared, - Mutability::Mut => ty::RefKind::Mut, + let region = self.translate_region(span, erase_regions, region)?; + let ty = self.translate_ty(span, erase_regions, ty)?; + let kind = if *mutability { + RefKind::Mut + } else { + RefKind::Shared }; - Ok(ty::Ty::Ref(region, Box::new(ty), kind)) + Ok(Ty::Ref(region, Box::new(ty), kind)) } - TyKind::RawPtr(ty_and_mut) => { + hax::Ty::RawPtr(ty_and_mut) => { trace!("RawPtr: {:?}", ty_and_mut); - let ty = self.translate_ty(region_translator, &ty_and_mut.ty)?; - let kind = match ty_and_mut.mutbl { - Mutability::Not => ty::RefKind::Shared, - Mutability::Mut => ty::RefKind::Mut, + let ty = self.translate_ty(span, erase_regions, &ty_and_mut.ty)?; + let kind = if ty_and_mut.mutbl { + RefKind::Mut + } else { + RefKind::Shared }; - Ok(ty::Ty::RawPtr(Box::new(ty), kind)) + Ok(Ty::RawPtr(Box::new(ty), kind)) } - TyKind::Tuple(substs) => { + hax::Ty::Tuple(substs) => { trace!("Tuple"); let mut params = vec![]; for param in substs.iter() { - let param_ty = self.translate_ty(region_translator, ¶m)?; + let param_ty = self.translate_ty(span, erase_regions, param)?; params.push(param_ty); } - Ok(ty::Ty::Adt( - ty::TypeId::Tuple, - Vec::new(), - params, - Vec::new(), - )) + Ok(Ty::Adt(TypeId::Tuple, GenericArgs::new_from_types(params))) } - TyKind::FnPtr(_) => { - trace!("FnPtr"); - unimplemented!(); - } - TyKind::Param(param) => { + hax::Ty::Param(param) => { // A type parameter, for example `T` in `fn f(x : T) {}`. // Note that this type parameter may actually have been // instantiated (in our environment, we may map it to another @@ -213,202 +304,217 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { trace!("Param"); // Retrieve the translation of the substituted type: - let ty = self.type_vars_map.get(¶m.index).unwrap(); - let ty = ty::Ty::TypeVar(*ty); + let var_id = self.type_vars_map.get(¶m.index).unwrap(); + let ty = Ty::TypeVar(var_id); Ok(ty) } - TyKind::Foreign(_) => { + hax::Ty::Foreign(id) => { trace!("Foreign"); - unimplemented!(); + error_or_panic!( + self, + span, + format!( + "Unsupported type: foreign type: {:?}", + id.rust_def_id.unwrap() + ) + ) } - TyKind::Infer(_) => { + hax::Ty::Infer(_) => { trace!("Infer"); - unimplemented!(); + error_or_panic!(self, span, "Unsupported type: infer type") } - TyKind::FnDef(_, _) => { - trace!("FnDef"); - unimplemented!(); - } - - TyKind::Dynamic(_, _, _) => { + hax::Ty::Dynamic(_, _, _) => { trace!("Dynamic"); - unimplemented!(); - } - TyKind::Closure(_, _) => { - trace!("Closure"); - unimplemented!(); + error_or_panic!(self, span, "Dynamic types are not supported yet") } - TyKind::Generator(_, _, _) | TyKind::GeneratorWitness(_) => { + hax::Ty::Generator(_, _, _) => { trace!("Generator"); - unimplemented!(); + error_or_panic!(self, span, "Generator types are not supported yet") } - TyKind::Error(_) => { - trace!("Error"); - unimplemented!(); - } - TyKind::Bound(_, _) => { + hax::Ty::Bound(_, _) => { trace!("Bound"); - unimplemented!(); + error_or_panic!(self, span, "Unexpected type kind: bound") } - TyKind::Placeholder(_) => { + hax::Ty::Placeholder(_) => { trace!("PlaceHolder"); - unimplemented!(); + error_or_panic!(self, span, "Unsupported type: placeholder") + } + hax::Ty::Arrow(box sig) => { + trace!("Arrow"); + error_assert!(self, span, sig.bound_vars.is_empty()); + let inputs = sig + .value + .inputs + .iter() + .map(|x| self.translate_ty(span, erase_regions, x)) + .try_collect()?; + let output = self.translate_ty(span, erase_regions, &sig.value.output)?; + Ok(Ty::Arrow(inputs, Box::new(output))) } - TyKind::GeneratorWitnessMIR(..) => { - trace!("GeneratorWitnessMIR"); - unimplemented!(); + hax::Ty::Error => { + trace!("Error"); + error_or_panic!(self, span, "Unknown error") + } + hax::Ty::Todo(s) => { + trace!("Todo: {s}"); + error_or_panic!(self, span, format!("Unsupported type: {:?}", s)) } } } - /// Translate a signature type, where the regions are not erased and use region - /// variable ids. - /// Simply calls [`translate_ty`](translate_ty) - pub(crate) fn translate_sig_ty(&mut self, ty: &Ty<'tcx>) -> Result { - // Borrowing issues: we have to clone the region vars map. - // This shouldn't cost us too much. In case of performance issues, - // we can turn the map into an im::map - let region_vars_map = self.region_vars_map.clone(); - self.translate_ty(&|r| translate_non_erased_region(®ion_vars_map, *r), ty) - } - - /// Translate a type where the regions are erased - /// Simply calls [translate_ty] - pub(crate) fn translate_ety(&mut self, ty: &Ty<'tcx>) -> Result { - self.translate_ty(&|r| translate_erased_region(*r), ty) - } - - fn translate_substs( + #[allow(clippy::type_complexity)] + pub fn translate_substs( &mut self, - region_translator: &dyn Fn(&rustc_middle::ty::RegionKind<'tcx>) -> R, + span: rustc_span::Span, + erase_regions: bool, used_params: Option>, - substs: &rustc_middle::ty::subst::SubstsRef<'tcx>, - ) -> Result<(Vec, Vec>, Vec)> - where - R: Clone + Eq, - { + substs: &[hax::GenericArg], + ) -> Result<(Vec, Vec, Vec), Error> { + trace!("{:?}", substs); // Filter the parameters - let mut param_i = 0; - let substs: Vec<(rustc_middle::ty::subst::GenericArg<'_>, u32)> = match used_params { - Option::None => substs - .iter() - .map(|p| { - let i = param_i; - param_i += 1; - (p, i) - }) - .collect(), - Option::Some(used_params) => substs - .iter() - .zip(used_params.into_iter()) - .filter_map(|(param, used)| { - let i = param_i; - param_i += 1; - if used { - Some((param, i)) - } else { - None - } - }) - .collect(), + let substs: Vec<&hax::GenericArg> = match used_params { + Option::None => substs.iter().collect(), + Option::Some(used_args) => { + error_assert!(self, span, substs.len() == used_args.len()); + substs + .iter() + .zip(used_args.into_iter()) + .filter_map(|(param, used)| if used { Some(param) } else { None }) + .collect() + } }; - let mut regions: Vec = vec![]; + let mut regions: Vec = vec![]; let mut params = vec![]; - let cgs = vec![]; - for (param, param_i) in substs.iter() { - trace!("Adt: param {}: {:?}", param_i, param); - match param.unpack() { - rustc_middle::ty::subst::GenericArgKind::Type(param_ty) => { - let param_ty = self.translate_ty(region_translator, ¶m_ty)?; + let mut cgs = vec![]; + for param in substs.iter() { + match param { + hax::GenericArg::Type(param_ty) => { + let param_ty = self.translate_ty(span, erase_regions, param_ty)?; params.push(param_ty); } - rustc_middle::ty::subst::GenericArgKind::Lifetime(region) => { - regions.push(region_translator(®ion)); + hax::GenericArg::Lifetime(region) => { + regions.push(self.translate_region(span, erase_regions, region)?); } - rustc_middle::ty::subst::GenericArgKind::Const(_) => { - unimplemented!(); + hax::GenericArg::Const(c) => { + cgs.push(self.translate_constant_expr_to_const_generic(span, c)?); } } } - Result::Ok((regions, params, cgs)) + Ok((regions, params, cgs)) + } + + pub fn translate_substs_and_trait_refs( + &mut self, + span: rustc_span::Span, + erase_regions: bool, + used_params: Option>, + substs: &[hax::GenericArg], + trait_refs: &[hax::ImplSource], + ) -> Result { + let (regions, types, const_generics) = + self.translate_substs(span, erase_regions, used_params, substs)?; + let trait_refs = self.translate_trait_impl_sources(span, erase_regions, trait_refs)?; + Ok(GenericArgs { + regions, + types, + const_generics, + trait_refs, + }) } /// Translate a type def id - pub(crate) fn translate_type_id(&mut self, def_id: DefId) -> ty::TypeId { + pub(crate) fn translate_type_id(&mut self, def_id: &hax::DefId) -> TypeId { trace!("{:?}", def_id); - if def_id.is_local() { - ty::TypeId::Adt(self.translate_type_decl_id(def_id)) + let rust_id = def_id.rust_def_id.unwrap(); + if rust_id.is_local() { + TypeId::Adt(self.translate_type_decl_id(rust_id)) } else { // Non-local: check if the type has primitive support // Retrieve the type name - let name = type_def_id_to_name(self.t_ctx.tcx, def_id); + let name = self.t_ctx.def_id_to_name(def_id); match assumed::get_type_id_from_name(&name) { Option::Some(id) => { // The type has primitive support - ty::TypeId::Assumed(id) + TypeId::Assumed(id) } Option::None => { // The type is external - ty::TypeId::Adt(self.translate_type_decl_id(def_id)) + TypeId::Adt(self.translate_type_decl_id(rust_id)) } } } } - /// Translate one local type definition which has not been flagged as opaque. - fn translate_transparent_type( + /// Translate the body of a type declaration. + /// + /// Note that the type may be external, in which case we translate the body + /// only if it is public (i.e., it is a public enumeration, or it is a + /// struct with only public fields). + fn translate_type_body( &mut self, - trans_id: ty::TypeDeclId::Id, - substs: &rustc_middle::ty::subst::SubstsRef<'tcx>, - ) -> ty::TypeDeclKind { + is_local: bool, + trans_id: TypeDeclId::Id, + adt: hax::AdtDef, + ) -> Result { trace!("{}", trans_id); + let def_span = self.t_ctx.tcx.def_span(adt.did.rust_def_id.unwrap()); + + // In case the type is external, check if we should consider the type as + // transparent (i.e., extract its body). If it is an enumeration, then yes + // (because the variants of public enumerations are public, together with their + // fields). If it is a structure, we check if all the fields are public. + let is_transparent = is_local + || match &adt.adt_kind { + hax::AdtKind::Enum => true, + hax::AdtKind::Struct => { + // Check the unique variant + error_assert!(self, def_span, adt.variants.raw.len() == 1); + adt.variants.raw[0] + .fields + .iter() + .all(|f| matches!(f.vis, hax::Visibility::Public)) + } + hax::AdtKind::Union => { + error_or_panic!(self, def_span, "Unions are not supported") + } + }; - // Retrieve the definition - trace!("{:?}", self.def_id); - let adt = self.t_ctx.tcx.adt_def(self.def_id); + if !is_transparent { + return Ok(TypeDeclKind::Opaque); + } - // Explore the variants - let mut var_id = ty::VariantId::Id::new(0); // Variant index - let mut variants: Vec = vec![]; - for var_def in adt.variants().iter() { + // The type is transparent: explore the variants + let mut var_id = VariantId::Id::new(0); // Variant index + let mut variants: Vec = vec![]; + let erase_regions = false; + for var_def in adt.variants.raw { trace!("variant {}: {:?}", var_id, var_def); - let mut fields: Vec = vec![]; - let mut field_id = ty::FieldId::Id::new(0); + let mut fields: Vec = vec![]; + let mut field_id = FieldId::Id::new(0); /* This is for sanity: check that either all the fields have names, or * none of them has */ let mut have_names: Option = Option::None; - for field_def in var_def.fields.iter() { + for field_def in var_def.fields.into_iter() { trace!("variant {}: field {}: {:?}", var_id, field_id, field_def); - - let ty = field_def.ty(self.t_ctx.tcx, substs); + let field_span = field_def.span.rust_span; // Translate the field type - let ty = self.translate_sig_ty(&ty).unwrap(); + let ty = self.translate_ty(field_span, erase_regions, &field_def.ty)?; // Retrieve the field name. - // Note that the only way to check if the user wrote the name or - // if it is just an integer generated by rustc is by checking if - // if is just made of numerals... - let field_name = field_def.ident(self.t_ctx.tcx).name.to_ident_string(); - let field_name = { - let field_id: std::result::Result = - field_name.parse(); - match field_id { - std::result::Result::Ok(_) => None, - std::result::Result::Err(_) => Some(field_name), - } - }; + let field_name = field_def.name; + // Sanity check match &have_names { Option::None => { have_names = match &field_name { @@ -417,15 +523,15 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { } } Option::Some(b) => { - assert!(*b == field_name.is_some()); + error_assert!(self, field_span, *b == field_name.is_some()); } }; // Translate the span information - let meta = self.translate_meta_from_rid(field_def.did); + let meta = self.translate_meta_from_rspan(field_def.span); // Store the field - let field = ty::Field { + let field = Field { meta, name: field_name.clone(), ty, @@ -435,35 +541,52 @@ impl<'tcx, 'ctx, 'ctx1> BodyTransCtx<'tcx, 'ctx, 'ctx1> { field_id.incr(); } - let meta = self.translate_meta_from_rid(var_def.def_id); - let variant_name = var_def.ident(self.t_ctx.tcx).name.to_ident_string(); - variants.push(ty::Variant { + let meta = self.translate_meta_from_rspan(var_def.span); + let variant_name = var_def.name; + variants.push(Variant { meta, name: variant_name, - fields: ty::FieldId::Vector::from(fields), + fields: FieldId::Vector::from(fields), }); var_id.incr(); } // Register the type - let type_def_kind: ty::TypeDeclKind = match adt.adt_kind() { - rustc_middle::ty::AdtKind::Struct => { - ty::TypeDeclKind::Struct(variants[0].fields.clone()) - } - rustc_middle::ty::AdtKind::Enum => { - ty::TypeDeclKind::Enum(ty::VariantId::Vector::from(variants)) - } - rustc_middle::ty::AdtKind::Union => { - unimplemented!(); + use hax::AdtKind; + let type_def_kind: TypeDeclKind = match adt.adt_kind { + AdtKind::Struct => TypeDeclKind::Struct(variants[0].fields.clone()), + AdtKind::Enum => TypeDeclKind::Enum(VariantId::Vector::from(variants)), + AdtKind::Union => { + let span = self.t_ctx.tcx.def_span(adt.did.rust_def_id.unwrap()); + error_or_panic!(self, span, "Union types are not supported") } }; - type_def_kind + Ok(type_def_kind) + } + + /// Sanity check: region names are pairwise distinct (this caused trouble + /// when generating names for the backward functions in Aeneas): at some + /// point, Rustc introduced names equal to `Some("'_")` for the anonymous + /// regions, instead of using `None` (we now check in [translate_region_name] + /// and ignore names equal to "'_"). + pub(crate) fn check_generics(&self) { + let mut s = std::collections::HashSet::new(); + for r in &self.region_vars { + let name = &r.name; + if name.is_some() { + let name = name.as_ref().unwrap(); + assert!( + !s.contains(name), + "Name \"{}\" used for different lifetimes", + name + ); + s.insert(name.clone()); + } + } } -} -impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Auxiliary helper. /// /// Translate the generics of a type definition. @@ -473,125 +596,132 @@ impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// /// Rem.: this seems simpler in [crate::translate_functions_to_ullbc]. /// TODO: compare and simplify/factorize? - fn translate_type_generics<'ctx1>( - &'ctx1 mut self, - def_id: DefId, - ) -> ( - BodyTransCtx<'tcx, 'ctx1, 'ctx>, - rustc_middle::ty::subst::SubstsRef<'tcx>, - ) { - // Check the generics - generics::check_type_generics(self.tcx, def_id); - - // Use a dummy substitution to instantiate the type parameters - let substs = rustc_middle::ty::subst::InternalSubsts::identity_for_item(self.tcx, def_id); - - // Initialize the body translation context - let mut bt_ctx = BodyTransCtx::new(def_id, self); - - for p in substs.iter() { - match p.unpack() { - rustc_middle::ty::subst::GenericArgKind::Type(param_ty) => { - // The type should be a Param: - match param_ty.kind() { - rustc_middle::ty::TyKind::Param(param_ty) => { - let _ = bt_ctx - .push_type_var(param_ty.index, param_ty.name.to_ident_string()); - } - _ => { - panic!("Inconsistent state"); - } + pub(crate) fn translate_generic_params(&mut self, def_id: DefId) -> Result<(), Error> { + let tcx = self.t_ctx.tcx; + let span = tcx.def_span(def_id); + + // We could use: TyCtxt::generics_of(DefId) + // But using the identity substitution is simpler. For instance, we can + // easily retrieve the type for the const parameters. + let substs = rustc_middle::ty::subst::InternalSubsts::identity_for_item(tcx, def_id) + .sinto(&self.hax_state); + + self.translate_generic_params_from_hax(span, &substs) + } + + pub(crate) fn translate_generic_params_from_hax( + &mut self, + span: rustc_span::Span, + substs: &Vec, + ) -> Result<(), Error> { + let erase_regions = false; + for p in substs { + use hax::GenericArg::*; + match p { + Type(p) => { + // The type should be a Param + if let hax::Ty::Param(p) = p { + let _ = self.push_type_var(p.index, p.name.clone()); + } else { + unreachable!("unexpected"); } } - rustc_middle::ty::subst::GenericArgKind::Lifetime(region) => { - let name = translate_region_name(®ion); - let _ = bt_ctx.push_region(*region, name); + Lifetime(region) => { + let name = translate_region_name(region); + let _ = self.push_region(region.clone(), name); } - rustc_middle::ty::subst::GenericArgKind::Const(c) => { + Const(c) => { // The type should be primitive, meaning it shouldn't contain variables, // non-primitive adts, etc. As a result, we can use an empty context. - let ty = bt_ctx.translate_ety(&c.ty()).unwrap(); + let ty = self.translate_ty(span, erase_regions, &c.ty)?; let ty = ty.to_literal(); - match c.kind() { - rustc_middle::ty::ConstKind::Param(cp) => { - let _ = bt_ctx.push_const_generic_var( - cp.index, - ty, - cp.name.to_ident_string(), - ); - } - _ => unreachable!(), + if let hax::ConstantExprKind::ConstRef { id: cp } = &*c.contents { + self.push_const_generic_var(cp.index, ty, cp.name.clone()); + } else { + unreachable!(); } } } } - // Sanity check: region names are pairwise distinct (this caused trouble - // when generating names for the backward functinos in Aeneas): at some - // point, Rustc introduced names equal to `Some("'_")` for the anonymous - // regions, instead of using `None` (we now check in [translate_region_name] - // and ignore names equal to "'_"). - { - let mut s = std::collections::HashSet::new(); - for r in &bt_ctx.region_vars { - let name = &r.name; - if name.is_some() { - let name = name.as_ref().unwrap(); - assert!(s.contains(name)); - s.insert(name.clone()); - } - } - } + // Sanity check + self.check_generics(); - (bt_ctx, substs) + Ok(()) } +} +impl<'tcx, 'ctx> TransCtx<'tcx, 'ctx> { /// Translate a type definition. /// /// Note that we translate the types one by one: we don't need to take into /// account the fact that some types are mutually recursive at this point /// (we will need to take that into account when generating the code in a file). - pub(crate) fn translate_type(&mut self, id: DefId) { - let trans_id = self.translate_type_decl_id(id); - let is_transparent = self.id_is_transparent(id); + pub(crate) fn translate_type(&mut self, rust_id: DefId) { + // TODO: for now, if there is an error while translating the parameters/ + // predicates of the declaration, we ignore it altogether, while we should + // save somewhere that we failed to extract it. + if self.translate_type_aux(rust_id).is_err() { + // TODO + } + } + + /// Auxliary helper to properly handle errors, see [translate_type]. + fn translate_type_aux(&mut self, rust_id: DefId) -> Result<(), Error> { + let trans_id = self.translate_type_decl_id(rust_id); + let is_transparent = self.id_is_transparent(rust_id); + + let mut bt_ctx = BodyTransCtx::new(rust_id, self); // Check and translate the generics - // TODO: use the body trans context as input, and don't return anything. - let (mut bt_ctx, substs) = self.translate_type_generics(id); - - // Check if the type is opaque or external, and delegate the translation - // of the "body" to the proper function - let kind = if !id.is_local() || !is_transparent { - // Opaque types are: - // - external types - // - local types flagged as opaque - ty::TypeDeclKind::Opaque + bt_ctx.translate_generic_params(rust_id)?; + + // Translate the predicates + bt_ctx.translate_predicates_solve_trait_obligations_of(None, rust_id)?; + + // Check if the type has been explicitely marked as opaque. + // If yes, ignore it, otherwise, dive into the body. Note that for + // external types we have to check the body: if the body is + // public, we translate it, otherwise we consider the type as opaque. + // For instance, because `core::option::Option` is public, we can + // manipulate its variants. If we encounter this type, we must retrieve + // its definition. + let is_local = rust_id.is_local(); + let kind = if !is_transparent { + TypeDeclKind::Opaque } else { - bt_ctx.translate_transparent_type(trans_id, &substs) + let adt = bt_ctx.t_ctx.tcx.adt_def(rust_id).sinto(&bt_ctx.hax_state); + match bt_ctx.translate_type_body(is_local, trans_id, adt) { + Ok(kind) => kind, + Err(err) => TypeDeclKind::Error(err.msg), + } }; // Register the type - let name = type_def_id_to_name(bt_ctx.t_ctx.tcx, id); - let region_params = bt_ctx.region_vars.clone(); - let type_params = bt_ctx.type_vars.clone(); - let const_generic_params = bt_ctx.const_generic_vars.clone(); + let name = bt_ctx + .t_ctx + .extended_def_id_to_name(&rust_id.sinto(&bt_ctx.hax_state)); + let generics = bt_ctx.get_generics(); // Translate the span information - let meta = bt_ctx.translate_meta_from_rid(id); + let meta = bt_ctx.translate_meta_from_rid(rust_id); - let type_def = ty::TypeDecl { + let type_def = TypeDecl { def_id: trans_id, meta, + is_local, name, - region_params, - type_params, - const_generic_params, + generics, + preds: bt_ctx.get_predicates(), kind, - regions_hierarchy: RegionGroups::new(), }; + trace!("translate_type: preds: {:?}", &type_def.preds); + trace!("{} -> {}", trans_id.to_string(), type_def.to_string()); self.type_defs.insert(trans_id, type_def); + + Ok(()) } } diff --git a/charon/src/types.rs b/charon/src/types.rs index 31a46b01..3aa76b28 100644 --- a/charon/src/types.rs +++ b/charon/src/types.rs @@ -1,10 +1,9 @@ -#![allow(dead_code)] - +pub use crate::gast::TraitItemName; use crate::meta::Meta; -use crate::names::TypeName; -use crate::regions_hierarchy::RegionGroups; +use crate::names::Name; pub use crate::types_utils::*; use crate::values::Literal; +use derivative::Derivative; use macros::{ generate_index_type, EnumAsGetters, EnumIsA, EnumToGetters, VariantIndexArity, VariantName, }; @@ -21,14 +20,14 @@ generate_index_type!(TypeVarId); generate_index_type!(TypeDeclId); generate_index_type!(VariantId); generate_index_type!(FieldId); -generate_index_type!(RegionVarId); +generate_index_type!(RegionId); generate_index_type!(ConstGenericVarId); generate_index_type!(GlobalDeclId); /// Type variable. /// We make sure not to mix variables and type variables by having two distinct /// definitions. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct TypeVar { /// Unique index identifying the variable pub index: TypeVarId::Id, @@ -37,16 +36,16 @@ pub struct TypeVar { } /// Region variable. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct RegionVar { /// Unique index identifying the variable - pub index: RegionVarId::Id, + pub index: RegionId::Id, /// Region name pub name: Option, } /// Const Generic Variable -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct ConstGenericVar { /// Unique index identifying the variable pub index: ConstGenericVarId::Id, @@ -56,26 +55,242 @@ pub struct ConstGenericVar { pub ty: LiteralTy, } -/// Region as used in a function's signatures (in which case we use region variable -/// ids) and in symbolic variables and projections (in which case we use region -/// ids). #[derive( - Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, EnumIsA, EnumAsGetters, Serialize, + Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord, EnumIsA, EnumAsGetters, Serialize, )] -pub enum Region { +pub enum Region { /// Static region Static, /// Non-static region. - Var(Rid), + Var(RegionId::Id), + /// Erased region + Erased, + /// For error reporting. + /// Can appear only if the option [CliOpts::continue_on_failure] is used. + Unknown, } -/// The type of erased regions. See [`Ty`](Ty) for more explanations. -/// We could use `()`, but having a dedicated type makes things more explicit. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] -pub enum ErasedRegion { - Erased, +/// Identifier of a trait instance. +/// This is derived from the trait resolution. +/// +/// Should be read as a path inside the trait clauses which apply to the current +/// definition. Note that every path designated by [TraitInstanceId] refers +/// to a *trait instance*, which is why the [Clause] variant may seem redundant +/// with some of the other variants. +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum TraitInstanceId { + /// + /// A specific implementation + TraitImpl(TraitImplId::Id), + /// + /// A specific builtin trait implementation like [core::marker::Sized] or + /// auto trait implementation like [core::marker::Syn]. + BuiltinOrAuto(TraitDeclId::Id), + /// + /// One of the local clauses. + /// + /// Example: + /// ```text + /// fn f(...) where T : Foo + /// ^^^^^^^ + /// Clause(0) + /// ``` + Clause(TraitClauseId::Id), + /// + /// A parent clause + /// + /// Remark: the [TraitDeclId::Id] gives the trait declaration which is + /// implemented by the instance id from which we take the parent clause + /// (see example below). It is not necessary and included for convenience. + /// + /// Example: + /// ```text + /// trait Foo1 {} + /// trait Foo2 { fn f(); } + /// + /// trait Bar : Foo1 + Foo2 {} + /// ^^^^ ^^^^ + /// parent clause 1 + /// parent clause 0 + /// + /// fn g(x : T) { + /// x.f() + /// ^^^^^ + /// Parent(Clause(0), Bar, 1)::f(x) + /// ^ + /// parent clause 1 of clause 0 + /// ^^^ + /// clause 0 implements Bar + /// } + /// ``` + ParentClause(Box, TraitDeclId::Id, TraitClauseId::Id), + /// + /// A clause bound in a trait item (typically a trait clause in an + /// associated type). + /// + /// Remark: the [TraitDeclId::Id] gives the trait declaration which is + /// implemented by the trait implementation from which we take the item + /// (see below). It is not necessary and provided for convenience. + /// + /// Example: + /// ```text + /// trait Foo { + /// type W: Bar0 + Bar1 // Bar1 contains a method bar1 + /// ^^^^ + /// this is the clause 1 applying to W + /// } + /// + /// fn f(x : T::W) { + /// x.bar1(); + /// ^^^^^^^ + /// ItemClause(Clause(0), Foo, W, 1) + /// ^^^^ + /// clause 1 from item W (from local clause 0) + /// ^^^ + /// local clause 0 implements Foo + /// } + /// ``` + /// + /// + ItemClause( + Box, + TraitDeclId::Id, + TraitItemName, + TraitClauseId::Id, + ), + /// Happens when we use a function pointer as an object implementing a + /// trait like `Fn` or `FnMut`. + /// + /// ```text + /// fn incr(x : u32) -> u32 { ... } + /// + /// Example: + /// fn f(a: [u32; 32]) -> [u32; 32] { + /// a.map(incr) + /// } + /// ``` + FnPointer(Box), + /// + /// Self, in case of trait declarations/implementations. + /// + /// Putting [Self] at the end on purpose, so that when ordering the clauses + /// we start with the other clauses (in particular, the local clauses). It + /// is useful to give priority to the local clauses when solving the trait + /// obligations which are fullfilled by the trait parameters. + SelfId, + /// Clause which hasn't been solved yet. + /// This happens when we register clauses in the context: solving some + /// trait obligations/references might require to refer to clauses which + /// haven't been registered yet. This variant is purely internal: after we + /// finished solving the trait obligations, all the remaining unsolved + /// clauses (in case we don't fail hard on error) are converted to [Unknown]. + Unsolved(TraitDeclId::Id, GenericArgs), + /// For error reporting. + /// Can appear only if the option [CliOpts::continue_on_failure] is used. + Unknown(String), +} + +/// A reference to a trait +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct TraitRef { + pub trait_id: TraitInstanceId, + pub generics: GenericArgs, + /// Not necessary, but useful + pub trait_decl_ref: TraitDeclRef, } +/// Reference to a trait declaration. +/// +/// About the generics, if we write: +/// ```text +/// impl Foo for String { ... } +/// ``` +/// +/// The substitution is: `[String, bool]`. +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct TraitDeclRef { + pub trait_id: TraitDeclId::Id, + pub generics: GenericArgs, +} + +/// .0 outlives .1 +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub struct OutlivesPred(pub T, pub U); + +pub type RegionOutlives = OutlivesPred; +pub type TypeOutlives = OutlivesPred; + +/// A constraint over a trait associated type. +/// +/// Example: +/// ```text +/// T : Foo +/// ^^^^^^^^^^ +/// ``` +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub struct TraitTypeConstraint { + pub trait_ref: TraitRef, + pub generics: GenericArgs, + pub type_name: TraitItemName, + pub ty: Ty, +} + +/// The predicates which apply to a definition +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub struct Predicates { + /// The first region in the pair outlives the second region + pub regions_outlive: Vec, + /// The type outlives the region + pub types_outlive: Vec, + /// Constraints over trait associated types + pub trait_type_constraints: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash, Ord, PartialOrd)] +pub struct GenericArgs { + pub regions: Vec, + pub types: Vec, + pub const_generics: Vec, + // TODO: rename to match [GenericParams]? + pub trait_refs: Vec, +} + +/// Generic parameters for a declaration. +/// We group the generics which come from the Rust compiler substitutions +/// (the regions, types and const generics) as well as the trait clauses. +/// The reason is that we consider that those are parameters that need to +/// be filled. We group in a different place the predicates which are not +/// trait clauses, because those enforce constraints but do not need to +/// be filled with witnesses/instances. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct GenericParams { + pub regions: RegionId::Vector, + pub types: TypeVarId::Vector, + pub const_generics: ConstGenericVarId::Vector, + // TODO: rename to match [GenericArgs]? + pub trait_clauses: TraitClauseId::Vector, +} + +generate_index_type!(TraitClauseId); +generate_index_type!(TraitDeclId); +generate_index_type!(TraitImplId); + +#[derive(Debug, Clone, Serialize, Derivative)] +#[derivative(PartialEq)] +pub struct TraitClause { + /// We use this id when solving trait constraints, to be able to refer + /// to specific where clauses when the selected trait actually is linked + /// to a parameter. + pub clause_id: TraitClauseId::Id, + #[derivative(PartialEq = "ignore")] + pub meta: Option, + pub trait_id: TraitDeclId::Id, + /// Remark: the trait refs list in the [generics] field should be empty. + pub generics: GenericArgs, +} + +impl Eq for TraitClause {} + /// A type declaration. /// /// Types can be opaque or transparent. @@ -94,18 +309,14 @@ pub struct TypeDecl { pub def_id: TypeDeclId::Id, /// Meta information associated with the type. pub meta: Meta, - pub name: TypeName, - pub region_params: RegionVarId::Vector, - pub type_params: TypeVarId::Vector, - pub const_generic_params: ConstGenericVarId::Vector, + /// [true] if the type decl is a local type decl, [false] if it comes from + /// an external crate. + pub is_local: bool, + pub name: Name, + pub generics: GenericParams, + pub preds: Predicates, /// The type kind: enum, struct, or opaque. pub kind: TypeDeclKind, - /// The lifetime's hierarchy between the different regions. - /// We initialize it to a dummy value, then compute it once the whole crate - /// has been translated. - /// - /// TODO: move to Aeneas - pub regions_hierarchy: RegionGroups, } #[derive(Debug, Clone, EnumIsA, EnumAsGetters, Serialize)] @@ -116,6 +327,9 @@ pub enum TypeDeclKind { /// /// Either a local type marked as opaque, or an external type. Opaque, + /// Used if an error happened during the extraction, and we don't panic + /// on error. + Error(String), } #[derive(Debug, Clone, Serialize)] @@ -129,10 +343,12 @@ pub struct Variant { pub struct Field { pub meta: Meta, pub name: Option, - pub ty: RTy, + pub ty: Ty, } -#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIsA, VariantName, Serialize)] +#[derive( + Debug, PartialEq, Eq, Copy, Clone, EnumIsA, VariantName, Serialize, Hash, Ord, PartialOrd, +)] pub enum IntegerTy { Isize, I8, @@ -148,7 +364,9 @@ pub enum IntegerTy { U128, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, VariantName, EnumIsA, Serialize)] +#[derive( + Debug, PartialEq, Eq, Clone, Copy, Hash, VariantName, EnumIsA, Serialize, Ord, PartialOrd, +)] pub enum RefKind { Mut, Shared, @@ -157,7 +375,20 @@ pub enum RefKind { /// Type identifier. /// /// Allows us to factorize the code for assumed types, adts and tuples -#[derive(Debug, PartialEq, Eq, Clone, VariantName, EnumAsGetters, EnumIsA, Serialize)] +#[derive( + Debug, + PartialEq, + Eq, + Clone, + Copy, + VariantName, + EnumAsGetters, + EnumIsA, + Serialize, + Hash, + Ord, + PartialOrd, +)] pub enum TypeId { /// A "regular" ADT type. /// @@ -189,6 +420,9 @@ pub type TypeDecls = TypeDeclId::Map; EnumAsGetters, VariantIndexArity, Serialize, + Hash, + Ord, + PartialOrd, )] pub enum LiteralTy { Integer(IntegerTy), @@ -198,7 +432,18 @@ pub enum LiteralTy { /// Const Generic Values. Either a primitive value, or a variable corresponding to a primitve value #[derive( - Debug, PartialEq, Eq, Clone, VariantName, EnumIsA, EnumAsGetters, VariantIndexArity, Serialize, + Debug, + PartialEq, + Eq, + Clone, + VariantName, + EnumIsA, + EnumAsGetters, + VariantIndexArity, + Serialize, + Hash, + Ord, + PartialOrd, )] pub enum ConstGeneric { /// A global constant @@ -210,29 +455,22 @@ pub enum ConstGeneric { } /// A type. -/// -/// Types are parameterized by a type parameter used for regions (or lifetimes). -/// The reason is that in MIR, regions are used in the function signatures but -/// are erased in the function bodies. We make this extremely explicit (and less -/// error prone) in our encoding by using two different types: [`Region`](Region) -/// and [`ErasedRegion`](ErasedRegion), the latter being an enumeration with only -/// one variant. #[derive( Debug, + Clone, PartialEq, Eq, - Clone, + Hash, VariantName, EnumIsA, EnumAsGetters, EnumToGetters, VariantIndexArity, Serialize, + Ord, + PartialOrd, )] -pub enum Ty -where - R: Clone + std::cmp::Eq, // TODO: do we really need to put those here? -{ +pub enum Ty { /// An ADT. /// Note that here ADTs are very general. They can be: /// - user-defined ADTs @@ -240,7 +478,7 @@ where /// - assumed types (includes some primitive types, e.g., arrays or slices) /// The information on the nature of the ADT is stored in (`TypeId`)[TypeId]. /// The last list is used encode const generics, e.g., the size of an array - Adt(TypeId, Vec, Vec>, Vec), + Adt(TypeId, GenericArgs), TypeVar(TypeVarId::Id), Literal(LiteralTy), /// The never type, for computations which don't return. It is sometimes @@ -256,48 +494,17 @@ where /// can be coerced to any type. /// TODO: but do we really use this type for variables?... Never, - // We don't support floating point numbers on purpose + // We don't support floating point numbers on purpose (for now) /// A borrow - Ref(R, Box>, RefKind), + Ref(Region, Box, RefKind), /// A raw pointer. - /// - /// We need this not only for unsafe code, but also to extract optimized - /// MIR: in optimized MIR, boxe dereferences and moves out of boxes is - /// desugared to very low-level code, which manipulates raw pointers - /// but also `std::ptr::Unique` and `std::ptr::NonNull`. In particular, - /// if `b` is a `Box`, `x := move *b` is compiled to something like this: - /// ```text - /// tmp = (((b.0: std::ptr::Unique).0: std::ptr::NonNull).0: *const T); - /// x = move (*tmp); - /// ``` - /// - /// Also, deallocation leads to the following code (this is independent of the - /// level of MIR): - /// ```text - /// alloc::alloc::box_free::( - /// move (b.0: std::ptr::Unique), - /// move (b.1: std::alloc::Global)) - /// ``` - /// For now, we detect this case (this is hardcoded in [crate::register] and - /// [crate::translate_functions_to_ullbc]) to rewrite it to `free(move b)`. - /// - /// TODO: maybe we should simply deactivate support for optimized code: who - /// wants to verify this? - RawPtr(Box>, RefKind), + RawPtr(Box, RefKind), + /// A trait type + TraitType(TraitRef, GenericArgs, TraitItemName), + /// Arrow type + Arrow(Vec, Box), } -/// Type with *R*egions. -/// -/// Used in function signatures and type definitions. -/// TODO: rename to sty (*signature* type). Region types are used by the -/// interpreter. -pub type RTy = Ty>; - -/// Type with *E*rased regions. -/// -/// Used in function bodies, "general" value types, etc. -pub type ETy = Ty; - /// Assumed types identifiers. /// /// WARNING: for now, all the assumed types are covariant in the generic @@ -308,16 +515,23 @@ pub type ETy = Ty; /// TODO: update to not hardcode the types (except `Box` maybe) and be more /// modular. /// TODO: move to assumed.rs? -#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIsA, EnumAsGetters, VariantName, Serialize)] +#[derive( + Debug, + PartialEq, + Eq, + Clone, + Copy, + EnumIsA, + EnumAsGetters, + VariantName, + Serialize, + Hash, + Ord, + PartialOrd, +)] pub enum AssumedTy { /// Boxes have a special treatment: we translate them as identity. Box, - /// Comes from the standard library - Vec, - /// Comes from the standard library - Option, - /// Comes from the standard library - Range, /// Comes from the standard library. See the comments for [Ty::RawPtr] /// as to why we have this here. PtrUnique, @@ -330,3 +544,69 @@ pub enum AssumedTy { /// Primitive type Str, } + +/// We use this to store information about the parameters in parent blocks. +/// This is necessary because in the definitions we store *all* the generics, +/// including those coming from the outer impl block. +/// +/// For instance: +/// ```text +/// impl Foo { +/// ^^^ +/// outer block generics +/// fn bar(...) { ... } +/// ^^^ +/// generics local to the function bar +/// } +/// ``` +/// +/// In `bar` we store the generics: `[T, U]`. +/// +/// We however sometimes need to make a distinction between those two kinds +/// of generics, in particular when manipulating traits. For instance: +/// +/// ```text +/// impl Foo for Bar { +/// fn baz(...) { ... } +/// } +/// +/// fn test(...) { +/// x.baz(...); // Here, we refer to the call as: +/// // > Foo::baz(...) +/// // If baz hadn't been a method implementation of a trait, +/// // we would have refered to it as: +/// // > baz(...) +/// // The reason is that with traits, we refer to the whole +/// // trait implementation (as if it were a structure), then +/// // pick a specific method inside (as if projecting a field +/// // from a structure). +/// } +/// ``` +/// +/// **Remark**: Rust only allows refering to the generics of the immediately +/// outer block. For this reason, when we need to store the information about +/// the generics of the outer block(s), we need to do it only for one level +/// (this definitely makes things simpler). +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ParamsInfo { + pub num_region_params: usize, + pub num_type_params: usize, + pub num_const_generic_params: usize, + pub num_trait_clauses: usize, + pub num_regions_outlive: usize, + pub num_types_outlive: usize, + pub num_trait_type_constraints: usize, +} + +/// A function signature. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct FunSig { + /// Is the function unsafe or not + pub is_unsafe: bool, + pub generics: GenericParams, + pub preds: Predicates, + /// Optional fields, for trait methods only (see the comments in [ParamsInfo]). + pub parent_params_info: Option, + pub inputs: Vec, + pub output: Ty, +} diff --git a/charon/src/types_utils.rs b/charon/src/types_utils.rs index 50d6ae59..b393b50e 100644 --- a/charon/src/types_utils.rs +++ b/charon/src/types_utils.rs @@ -1,23 +1,31 @@ //! This file groups everything which is linked to implementations about [crate::types] -#![allow(dead_code)] - use crate::assumed::get_name_from_type_id; +use crate::common::TAB_INCR; use crate::formatter::Formatter; use crate::types::*; -use crate::ullbc_ast::GlobalDeclId; -use crate::values::Literal; +use crate::ullbc_ast::*; +use crate::values::*; +use hax_frontend_exporter as hax; use im::{HashMap, OrdSet}; use macros::make_generic_in_borrows; -use rustc_middle::ty::{IntTy, UintTy}; use std::iter::FromIterator; use std::iter::Iterator; -pub type RegionSubst = HashMap; -pub type TypeSubst = HashMap>; -/// Type substitution where the regions are erased -pub type ETypeSubst = TypeSubst; +pub type RegionSubst = HashMap; +pub type TypeSubst = HashMap; pub type ConstGenericSubst = HashMap; +// TODO: should we just put all the potential constraints we need in there? +pub trait TypeFormatter = Formatter + + Formatter + + Formatter + + Formatter + + Formatter + + Formatter + + Formatter + + Formatter + + Formatter; + impl ConstGenericVarId::Id { pub fn substitute( &self, @@ -40,41 +48,34 @@ impl ConstGeneric { } } -impl RegionVarId::Id { - pub fn substitute(&self, rsubst: &RegionSubst) -> R - where - R: Clone, - { - rsubst.get(self).unwrap().clone() +impl RegionId::Id { + pub fn substitute(&self, rsubst: &RegionSubst) -> Region { + *rsubst.get(self).unwrap() } } -impl Region { +impl Region { pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter, + T: Formatter, { match self { Region::Static => "'static".to_string(), Region::Var(id) => ctx.format_object(*id), + Region::Erased => "'_".to_string(), + Region::Unknown => "'_UNKNOWN_".to_string(), } } } -impl Region { - pub fn substitute( - &self, - rsubst: &HashMap>, - ) -> Region { - match self { - Region::Static => Region::Static, - Region::Var(id) => *rsubst.get(id).unwrap(), - } +impl Region { + pub fn substitute(&self, rsubst: &HashMap) -> Region { + *rsubst.get(self).unwrap() } - pub fn contains_var(&self, rset: &OrdSet) -> bool { + pub fn contains_var(&self, rset: &OrdSet) -> bool { match self { - Region::Static => false, + Region::Static | Region::Erased | Region::Unknown => false, Region::Var(id) => rset.contains(id), } } @@ -115,119 +116,451 @@ impl std::string::ToString for ConstGenericVar { } } -impl TypeDecl { - /// The variant id should be `None` if it is a structure and `Some` if it - /// is an enumeration. - pub fn get_fields(&self, variant_id: Option) -> &FieldId::Vector { - match &self.kind { - TypeDeclKind::Enum(variants) => &variants.get(variant_id.unwrap()).unwrap().fields, - TypeDeclKind::Struct(fields) => { - assert!(variant_id.is_none()); - fields +impl GenericParams { + pub fn len(&self) -> usize { + let GenericParams { + regions, + types, + const_generics, + trait_clauses, + } = self; + regions.len() + types.len() + const_generics.len() + trait_clauses.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn empty() -> Self { + GenericParams { + regions: RegionId::Vector::new(), + types: TypeVarId::Vector::new(), + const_generics: ConstGenericVarId::Vector::new(), + trait_clauses: TraitClauseId::Vector::new(), + } + } + + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + if self.is_empty() { + "".to_string() + } else { + let mut params = Vec::new(); + let GenericParams { + regions, + types, + const_generics, + trait_clauses, + } = self; + for x in regions { + params.push(x.to_string()); } - TypeDeclKind::Opaque => { - unreachable!("Opaque type") + for x in types { + params.push(x.to_string()); } + for x in const_generics { + params.push(x.to_string()); + } + for x in trait_clauses { + params.push(x.fmt_with_ctx(ctx)); + } + format!("<{}>", params.join(", ")) } } - /// Instantiate the fields of every variant of a type definition. - /// - /// Return an option: `Some` if we have access to the type definition, - /// `None` if the type is opaque. - pub fn get_instantiated_variants( - &self, - inst_regions: &Vec>, - inst_types: &Vec, - ) -> Option>> { - // Introduce the substitutions - let r_subst = make_region_subst( - self.region_params.iter().map(|x| x.index), - inst_regions.iter(), - ); - let ty_subst = make_type_subst(self.type_params.iter().map(|x| x.index), inst_types.iter()); + pub fn fmt_with_ctx_with_trait_clauses(&self, ctx: &C) -> (String, Vec) + where + C: TypeFormatter, + { + let mut params = Vec::new(); + let GenericParams { + regions, + types, + const_generics, + trait_clauses, + } = self; + for x in regions { + params.push(x.to_string()); + } + for x in types { + params.push(x.to_string()); + } + for x in const_generics { + params.push(x.to_string()); + } + let params = if params.is_empty() { + "".to_string() + } else { + format!("<{}>", params.join(", ")) + }; - match &self.kind { - TypeDeclKind::Struct(fields) => { - Option::Some(VariantId::Vector::from(vec![FieldId::Vector::from_iter( - fields - .iter() - .map(|f| f.ty.substitute_regions_types(&r_subst, &ty_subst)), - )])) + let mut clauses = Vec::new(); + for x in trait_clauses { + clauses.push(x.fmt_with_ctx(ctx)); + } + (params, clauses) + } +} + +/// [num_parent_clauses]: we store in the definitions all the clauses +/// they have access to, which includes the clauses inherited from the parent. +/// This can be confusing: we insert a delimiter between the inherited clauses +/// and the local clauses. +pub fn fmt_where_clauses(tab: &str, num_parent_clauses: usize, clauses: Vec) -> String { + if clauses.is_empty() { + "".to_string() + } else { + let mut clauses = clauses + .iter() + .map(|x| format!("\n{tab}{TAB_INCR}{x},")) + .collect::>(); + if num_parent_clauses > 0 { + let local_clauses = clauses.split_off(num_parent_clauses); + + let delim1 = if local_clauses.is_empty() { + "".to_string() + } else { + format!("\n{tab}{TAB_INCR}// Local clauses:") + }; + + let delim0 = if clauses.is_empty() { + "".to_string() + } else { + format!("\n{tab}{TAB_INCR}// Inherited clauses:") + }; + + let clauses = clauses.join(""); + let local_clauses = local_clauses.join(""); + format!("\n{tab}where{delim0}{clauses}{delim1}{local_clauses}") + } else { + let clauses = clauses.join(""); + format!("\n{tab}where{clauses}") + } + } +} + +impl TraitTypeConstraint { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let trait_ref = self.trait_ref.fmt_with_ctx(ctx); + let generics = self.generics.fmt_with_ctx_split_trait_refs(ctx); + let ty = self.ty.fmt_with_ctx(ctx); + format!("{}{}::{} = {}", trait_ref, generics, self.type_name, ty) + } +} + +impl Predicates { + pub fn is_empty(&self) -> bool { + let Predicates { + regions_outlive, + types_outlive, + trait_type_constraints, + } = self; + regions_outlive.is_empty() && types_outlive.is_empty() && trait_type_constraints.is_empty() + } +} + +pub fn fmt_where_clauses_with_ctx( + ctx: &C, + tab: &str, + info: &Option, + mut trait_clauses: Vec, + preds: &Predicates, +) -> String +where + C: TypeFormatter, +{ + let mut types_outlive: Vec<_> = preds + .types_outlive + .iter() + .map(|OutlivesPred(x, y)| format!("{} : {}", x.fmt_with_ctx(ctx), y.fmt_with_ctx(ctx))) + .collect(); + let mut regions_outlive: Vec<_> = preds + .regions_outlive + .iter() + .map(|OutlivesPred(x, y)| format!("{} : {}", x.fmt_with_ctx(ctx), y.fmt_with_ctx(ctx))) + .collect(); + let mut type_constraints: Vec<_> = preds + .trait_type_constraints + .iter() + .map(|x| x.fmt_with_ctx(ctx)) + .collect(); + match info { + None => { + let clauses: Vec<_> = trait_clauses + .into_iter() + .chain(types_outlive.into_iter()) + .chain(regions_outlive.into_iter()) + .chain(type_constraints.into_iter()) + .collect(); + fmt_where_clauses(tab, 0, clauses) + } + Some(info) => { + // Below: definitely not efficient nor convenient, but it is not really + // important + let local_clauses: Vec<_> = trait_clauses + .split_off(info.num_trait_clauses) + .into_iter() + .chain(regions_outlive.split_off(info.num_regions_outlive)) + .chain(types_outlive.split_off(info.num_types_outlive).into_iter()) + .chain( + type_constraints + .split_off(info.num_trait_type_constraints) + .into_iter(), + ) + .collect(); + let inherited_clauses: Vec<_> = trait_clauses + .into_iter() + .chain(regions_outlive.into_iter()) + .chain(types_outlive.into_iter()) + .chain(type_constraints.into_iter()) + .collect(); + let num_inherited = inherited_clauses.len(); + let all_clauses: Vec<_> = inherited_clauses + .into_iter() + .chain(local_clauses.into_iter()) + .collect(); + fmt_where_clauses(tab, num_inherited, all_clauses) + } + } +} + +impl GenericArgs { + pub fn len(&self) -> usize { + let GenericArgs { + regions, + types, + const_generics, + trait_refs, + } = self; + regions.len() + types.len() + const_generics.len() + trait_refs.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn empty() -> Self { + GenericArgs { + regions: Vec::new(), + types: Vec::new(), + const_generics: Vec::new(), + trait_refs: Vec::new(), + } + } + + pub fn new_from_types(types: Vec) -> Self { + GenericArgs { + regions: Vec::new(), + types, + const_generics: Vec::new(), + trait_refs: Vec::new(), + } + } + + pub fn new( + regions: Vec, + types: Vec, + const_generics: Vec, + trait_refs: Vec, + ) -> Self { + GenericArgs { + regions, + types, + const_generics, + trait_refs, + } + } + + pub(crate) fn fmt_with_ctx_no_brackets(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let mut params = Vec::new(); + let GenericArgs { + regions, + types, + const_generics, + trait_refs, + } = self; + for x in regions { + params.push(x.fmt_with_ctx(ctx)); + } + for x in types { + params.push(x.fmt_with_ctx(ctx)); + } + for x in const_generics { + params.push(x.fmt_with_ctx(ctx)); + } + for x in trait_refs { + params.push(x.fmt_with_ctx(ctx)) + } + params.join(", ") + } + + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + if self.is_empty() { + "".to_string() + } else { + format!("<{}>", self.fmt_with_ctx_no_brackets(ctx),) + } + } + + pub fn fmt_with_ctx_split_trait_refs(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let mut params = Vec::new(); + let GenericArgs { + regions, + types, + const_generics, + trait_refs, + } = self; + for x in regions { + params.push(x.fmt_with_ctx(ctx)); + } + for x in types { + params.push(x.fmt_with_ctx(ctx)); + } + for x in const_generics { + params.push(x.fmt_with_ctx(ctx)); + } + let params = if params.is_empty() { + "".to_string() + } else { + format!("<{}>", params.join(", ")) + }; + + let mut clauses = Vec::new(); + for x in trait_refs { + clauses.push(x.fmt_with_ctx(ctx)); + } + let clauses = if clauses.is_empty() { + "".to_string() + } else { + format!("[{}]", clauses.join(", ")) + }; + format!("{params}{clauses}") + } +} + +impl TraitClause { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let clause_id = ctx.format_object(self.clause_id); + let trait_id = ctx.format_object(self.trait_id); + let generics = self.generics.fmt_with_ctx(ctx); + format!("[{clause_id}]: {trait_id}{generics}") + } +} + +impl TraitInstanceId { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + match self { + TraitInstanceId::SelfId => "Self".to_string(), + TraitInstanceId::ParentClause(id, _decl_id, clause_id) => { + let id = id.fmt_with_ctx(ctx); + // Using on purpose [to_pretty_string] instead of [format_object]: + // the clause is local to the associated type, so it should not + // be referenced in the current context. + let clause = clause_id.to_pretty_string(); + format!("(parents({id})::[{clause}])") } - TypeDeclKind::Enum(variants) => { - Option::Some(VariantId::Vector::from_iter(variants.iter().map(|v| { - FieldId::Vector::from_iter( - v.fields - .iter() - .map(|f| f.ty.substitute_regions_types(&r_subst, &ty_subst)), - ) - }))) + TraitInstanceId::ItemClause(id, _decl_id, type_name, clause_id) => { + let id = id.fmt_with_ctx(ctx); + // Using on purpose [to_pretty_string] instead of [format_object]: + // the clause is local to the associated type, so it should not + // be referenced in the current context. + let clause = clause_id.to_pretty_string(); + format!("({id}::{type_name}::[{clause}])") + } + TraitInstanceId::TraitImpl(id) => ctx.format_object(*id), + TraitInstanceId::Clause(id) => ctx.format_object(*id), + TraitInstanceId::BuiltinOrAuto(id) => ctx.format_object(*id), + TraitInstanceId::FnPointer(box ty) => { + format!("(fn_ptr:{})", ty.fmt_with_ctx(ctx)) + } + TraitInstanceId::Unsolved(trait_id, generics) => { + format!( + "Unsolved({}{})", + ctx.format_object(*trait_id), + generics.fmt_with_ctx(ctx), + ) } - TypeDeclKind::Opaque => Option::None, + TraitInstanceId::Unknown(msg) => format!("UNKNOWN({msg})"), } } +} - /// The variant id should be `None` if it is a structure and `Some` if it - /// is an enumeration. - pub fn get_erased_regions_instantiated_field_types( - &self, - variant_id: Option, - inst_types: &Vec, - cgs: &Vec, - ) -> Vec { - // Introduce the substitution - let ty_subst = make_type_subst(self.type_params.iter().map(|x| x.index), inst_types.iter()); - let cg_subst = make_cg_subst( - self.const_generic_params.iter().map(|x| x.index), - cgs.iter(), - ); - - let fields = self.get_fields(variant_id); - let field_types: Vec = fields - .iter() - .map(|f| f.ty.erase_regions_substitute_types(&ty_subst, &cg_subst)) - .collect(); +impl TraitRef { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let trait_id = self.trait_id.fmt_with_ctx(ctx); + let generics = self.generics.fmt_with_ctx_split_trait_refs(ctx); + format!("{trait_id}{generics}") + } +} - Vec::from(field_types) +impl TraitDeclRef { + pub fn fmt_with_ctx(&self, ctx: &C) -> String + where + C: TypeFormatter, + { + let trait_id = ctx.format_object(self.trait_id); + let generics = self.generics.fmt_with_ctx_split_trait_refs(ctx); + format!("{trait_id}{generics}") } +} +impl TypeDecl { /// The variant id should be `None` if it is a structure and `Some` if it /// is an enumeration. - pub fn get_erased_regions_instantiated_field_type( + #[allow(clippy::result_unit_err)] + pub fn get_fields( &self, variant_id: Option, - inst_types: &Vec, - cgs: &Vec, - field_id: FieldId::Id, - ) -> ETy { - // Introduce the substitution - let ty_subst = make_type_subst(self.type_params.iter().map(|x| x.index), inst_types.iter()); - let cg_subst = make_cg_subst( - self.const_generic_params.iter().map(|x| x.index), - cgs.iter(), - ); - - let fields = self.get_fields(variant_id); - let field_type = fields - .get(field_id) - .unwrap() - .ty - .erase_regions() - .substitute_types(&ty_subst, &cg_subst); - field_type + ) -> Result<&FieldId::Vector, ()> { + match &self.kind { + TypeDeclKind::Enum(variants) => Ok(&variants.get(variant_id.unwrap()).unwrap().fields), + TypeDeclKind::Struct(fields) => { + assert!(variant_id.is_none()); + Ok(fields) + } + TypeDeclKind::Opaque => { + unreachable!("Opaque type") + } + TypeDeclKind::Error(_) => Err(()), + } } - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &'a T) -> String + pub fn fmt_with_ctx(&self, ctx: &C) -> String where - T: Formatter - + Formatter - + Formatter<&'a Region> - + Formatter - + Formatter - + Formatter, + C: TypeFormatter, { - let params = TypeDecl::fmt_params(&self.region_params, &self.type_params); + let (params, trait_clauses) = self.generics.fmt_with_ctx_with_trait_clauses(ctx); + // Predicates + let eq_space = if trait_clauses.is_empty() && self.preds.is_empty() { + " ".to_string() + } else { + "\n ".to_string() + }; + let preds = fmt_where_clauses_with_ctx(ctx, " ", &None, trait_clauses, &self.preds); + match &self.kind { TypeDeclKind::Struct(fields) => { if !fields.is_empty() { @@ -236,9 +569,15 @@ impl TypeDecl { .map(|f| format!("\n {}", f.fmt_with_ctx(ctx))) .collect(); let fields = fields.join(","); - format!("struct {}{} = {{{}\n}}", self.name, params, fields) + format!( + "struct {}{params}{preds}{eq_space}=\n{{{fields}\n}}", + self.name.fmt_with_ctx(ctx) + ) } else { - format!("struct {}{} = {{}}", self.name, params) + format!( + "struct {}{params}{preds}{eq_space}= {{}}", + self.name.fmt_with_ctx(ctx) + ) } } TypeDeclKind::Enum(variants) => { @@ -247,42 +586,34 @@ impl TypeDecl { .map(|v| format!("| {}", v.fmt_with_ctx(ctx))) .collect(); let variants = variants.join("\n"); - format!("enum {}{} =\n{}\n", self.name, params, variants) + format!( + "enum {}{params}{preds}{eq_space}=\n{variants}\n", + self.name.fmt_with_ctx(ctx) + ) + } + TypeDeclKind::Opaque => { + format!("opaque type {}{params}{preds}", self.name.fmt_with_ctx(ctx)) + } + TypeDeclKind::Error(msg) => { + format!( + "opaque type {}{params}{preds} = ERROR({msg})", + self.name.fmt_with_ctx(ctx), + ) } - TypeDeclKind::Opaque => format!("opaque type {}{}", self.name, params), - } - } - - fn fmt_params( - region_params: &RegionVarId::Vector, - type_params: &TypeVarId::Vector, - ) -> String { - if region_params.len() + type_params.len() > 0 { - let regions = region_params.iter().map(|r| r.to_string()); - let type_params = type_params.iter().map(|p| p.to_string()); - let params: Vec = regions.chain(type_params).collect(); - format!("<{}>", params.join(", ")) - } else { - "".to_string() } } } impl std::string::ToString for TypeDecl { fn to_string(&self) -> String { - self.fmt_with_ctx(&IncompleteFormatter { def: self }) + self.fmt_with_ctx(&DummyFormatter {}) } } impl Variant { - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &'a T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter<&'a Region> - + Formatter - + Formatter - + Formatter, + T: TypeFormatter, { let fields: Vec = self.fields.iter().map(|f| f.fmt_with_ctx(ctx)).collect(); let fields = fields.join(", "); @@ -291,14 +622,9 @@ impl Variant { } impl Field { - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &'a T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter<&'a Region> - + Formatter - + Formatter - + Formatter, + T: TypeFormatter, { match &self.name { Option::Some(name) => format!("{}: {}", name, self.ty.fmt_with_ctx(ctx)), @@ -320,25 +646,27 @@ impl std::string::ToString for Field { } impl IntegerTy { - pub fn rust_int_ty_to_integer_ty(ty: IntTy) -> IntegerTy { + pub fn rust_int_ty_to_integer_ty(ty: hax::IntTy) -> IntegerTy { + use hax::IntTy::*; match ty { - IntTy::Isize => IntegerTy::Isize, - IntTy::I8 => IntegerTy::I8, - IntTy::I16 => IntegerTy::I16, - IntTy::I32 => IntegerTy::I32, - IntTy::I64 => IntegerTy::I64, - IntTy::I128 => IntegerTy::I128, + Isize => IntegerTy::Isize, + I8 => IntegerTy::I8, + I16 => IntegerTy::I16, + I32 => IntegerTy::I32, + I64 => IntegerTy::I64, + I128 => IntegerTy::I128, } } - pub fn rust_uint_ty_to_integer_ty(ty: UintTy) -> IntegerTy { + pub fn rust_uint_ty_to_integer_ty(ty: hax::UintTy) -> IntegerTy { + use hax::UintTy::*; match ty { - UintTy::Usize => IntegerTy::Usize, - UintTy::U8 => IntegerTy::U8, - UintTy::U16 => IntegerTy::U16, - UintTy::U32 => IntegerTy::U32, - UintTy::U64 => IntegerTy::U64, - UintTy::U128 => IntegerTy::U128, + Usize => IntegerTy::Usize, + U8 => IntegerTy::U8, + U16 => IntegerTy::U16, + U32 => IntegerTy::U32, + U64 => IntegerTy::U64, + U128 => IntegerTy::U128, } } @@ -379,47 +707,65 @@ impl IntegerTy { } impl TypeVarId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@T{self}") } } impl TypeDeclId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@Adt{self}") } } impl VariantId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@Variant{self}") } } impl FieldId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@Field{self}") } } -impl RegionVarId::Id { - pub fn to_pretty_string(&self) -> String { +impl RegionId::Id { + pub fn to_pretty_string(self) -> String { format!("@R{self}") } } impl ConstGenericVarId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@Const{self}") } } impl GlobalDeclId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@Global{self}") } } +impl TraitClauseId::Id { + pub fn to_pretty_string(self) -> String { + format!("@TraitClause{self}") + } +} + +impl TraitDeclId::Id { + pub fn to_pretty_string(self) -> String { + format!("@TraitDecl{self}") + } +} + +impl TraitImplId::Id { + pub fn to_pretty_string(self) -> String { + format!("@TraitImpl{self}") + } +} + impl std::string::ToString for LiteralTy { fn to_string(&self) -> String { match self { @@ -450,26 +796,28 @@ impl std::fmt::Display for IntegerTy { } // IntTy is not defined in the current crate -pub fn intty_to_string(ty: IntTy) -> String { +pub fn intty_to_string(ty: hax::IntTy) -> String { + use hax::IntTy::*; match ty { - IntTy::Isize => "isize".to_string(), - IntTy::I8 => "i8".to_string(), - IntTy::I16 => "i16".to_string(), - IntTy::I32 => "i32".to_string(), - IntTy::I64 => "i64".to_string(), - IntTy::I128 => "i128".to_string(), + Isize => "isize".to_string(), + I8 => "i8".to_string(), + I16 => "i16".to_string(), + I32 => "i32".to_string(), + I64 => "i64".to_string(), + I128 => "i128".to_string(), } } // UintTy is not defined in the current crate -fn uintty_to_string(ty: UintTy) -> String { +pub fn uintty_to_string(ty: hax::UintTy) -> String { + use hax::UintTy::*; match ty { - UintTy::Usize => "usize".to_string(), - UintTy::U8 => "u8".to_string(), - UintTy::U16 => "u16".to_string(), - UintTy::U32 => "u32".to_string(), - UintTy::U64 => "u64".to_string(), - UintTy::U128 => "u128".to_string(), + Usize => "usize".to_string(), + U8 => "u8".to_string(), + U16 => "u16".to_string(), + U32 => "u32".to_string(), + U64 => "u64".to_string(), + U128 => "u128".to_string(), } } @@ -499,25 +847,22 @@ impl ConstGeneric { } } -impl Ty -where - R: Clone + Eq, -{ +impl Ty { /// Return true if it is actually unit (i.e.: 0-tuple) pub fn is_unit(&self) -> bool { match self { - Ty::Adt(TypeId::Tuple, regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(cgs.is_empty()); - tys.is_empty() + Ty::Adt(TypeId::Tuple, args) => { + assert!(args.regions.is_empty()); + assert!(args.const_generics.is_empty()); + args.types.is_empty() } _ => false, } } /// Return the unit type - pub fn mk_unit() -> Ty { - Ty::Adt(TypeId::Tuple, Vec::new(), Vec::new(), Vec::new()) + pub fn mk_unit() -> Ty { + Ty::Adt(TypeId::Tuple, GenericArgs::empty()) } /// Return true if this is a scalar type @@ -547,36 +892,21 @@ where /// We take an optional type context to be able to implement the Display /// trait, in which case there is no type context available and we print /// the ADT ids rather than their names. - pub fn fmt_with_ctx<'a, 'b, T>(&'a self, ctx: &'b T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - R: 'a, - T: Formatter - + Formatter - + Formatter - + Formatter - + Formatter<&'a R>, + T: TypeFormatter, { match self { - Ty::Adt(id, regions, inst_types, cgs) => { + Ty::Adt(id, generics) => { let adt_ident = id.fmt_with_ctx(ctx); - let num_params = regions.len() + inst_types.len(); - - let regions: Vec = regions.iter().map(|r| ctx.format_object(r)).collect(); - let mut types: Vec = - inst_types.iter().map(|ty| ty.fmt_with_ctx(ctx)).collect(); - let mut cgs: Vec = cgs.iter().map(|cg| cg.fmt_with_ctx(ctx)).collect(); - let mut all_params = regions; - all_params.append(&mut types); - all_params.append(&mut cgs); - let all_params = all_params.join(", "); - if id.is_tuple() { - format!("({all_params})") - } else if num_params > 0 { - format!("{adt_ident}<{all_params}>") + assert!(generics.trait_refs.is_empty()); + let generics = generics.fmt_with_ctx_no_brackets(ctx); + format!("({generics})") } else { - adt_ident + let generics = generics.fmt_with_ctx(ctx); + format!("{adt_ident}{generics}") } } Ty::TypeVar(id) => ctx.format_object(*id), @@ -584,231 +914,186 @@ where Ty::Never => "!".to_string(), Ty::Ref(r, ty, kind) => match kind { RefKind::Mut => { - format!("&{} mut ({})", ctx.format_object(r), ty.fmt_with_ctx(ctx)) + format!("&{} mut ({})", r.fmt_with_ctx(ctx), ty.fmt_with_ctx(ctx)) } RefKind::Shared => { - format!("&{} ({})", ctx.format_object(r), ty.fmt_with_ctx(ctx)) + format!("&{} ({})", r.fmt_with_ctx(ctx), ty.fmt_with_ctx(ctx)) } }, Ty::RawPtr(ty, kind) => match kind { RefKind::Mut => format!("*const {}", ty.fmt_with_ctx(ctx)), RefKind::Shared => format!("*mut {}", ty.fmt_with_ctx(ctx)), }, - } - } - - /// Return true if the type is Box - pub fn is_box(&self) -> bool { - match self { - Ty::Adt(TypeId::Assumed(AssumedTy::Box), regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - true + Ty::TraitType(trait_ref, substs, name) => { + format!( + "{}{}::{name}", + trait_ref.fmt_with_ctx(ctx), + substs.fmt_with_ctx_split_trait_refs(ctx) + ) } - _ => false, - } - } - - pub fn as_box(&self) -> Option<&Ty> { - match self { - Ty::Adt(TypeId::Assumed(AssumedTy::Box), regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - Some(tys.get(0).unwrap()) + Ty::Arrow(inputs, box output) => { + let inputs = inputs + .iter() + .map(|x| x.fmt_with_ctx(ctx)) + .collect::>() + .join(", "); + if output.is_unit() { + format!("fn({inputs})") + } else { + let output = output.fmt_with_ctx(ctx); + format!("fn({inputs}) -> {output}") + } } - _ => None, } } - /// Return true if the type is Vec - pub fn is_vec(&self) -> bool { + /// Return true if the type is Box + pub fn is_box(&self) -> bool { match self { - Ty::Adt(TypeId::Assumed(AssumedTy::Vec), regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); + Ty::Adt(TypeId::Assumed(AssumedTy::Box), generics) => { + assert!(generics.regions.is_empty()); + assert!(generics.types.len() == 1); + assert!(generics.const_generics.is_empty()); true } _ => false, } } - pub fn as_vec(&self) -> Option<&Ty> { + pub fn as_box(&self) -> Option<&Ty> { match self { - Ty::Adt(TypeId::Assumed(AssumedTy::Vec), regions, tys, cgs) => { - assert!(regions.is_empty()); - assert!(tys.len() == 1); - assert!(cgs.is_empty()); - Some(tys.get(0).unwrap()) + Ty::Adt(TypeId::Assumed(AssumedTy::Box), generics) => { + assert!(generics.regions.is_empty()); + assert!(generics.types.len() == 1); + assert!(generics.const_generics.is_empty()); + Some(generics.types.get(0).unwrap()) } _ => None, } } } -impl Ty> { +impl Ty { /// Returns `true` if the type contains one of the regions listed /// in the set - pub fn contains_region_var(&self, rset: &OrdSet) -> bool { + /// TODO: reimplement this with visitors + pub fn contains_region_var(&self, rset: &OrdSet) -> bool { match self { Ty::TypeVar(_) => false, Ty::Literal(_) | Ty::Never => false, Ty::Ref(r, ty, _) => r.contains_var(rset) || ty.contains_region_var(rset), Ty::RawPtr(ty, _) => ty.contains_region_var(rset), - Ty::Adt(_, regions, tys, _) => regions - .iter() - .any(|r| r.contains_var(rset) || tys.iter().any(|x| x.contains_region_var(rset))), + Ty::TraitType(_, generics, _) | Ty::Adt(_, generics) => { + // For the trait type case: we are checking the projected type, + // so we don't need to explore the trait ref + generics.regions.iter().any(|r| { + r.contains_var(rset) + || generics.types.iter().any(|x| x.contains_region_var(rset)) + }) + } + Ty::Arrow(inputs, box output) => { + inputs.iter().any(|x| x.contains_region_var(rset)) + || output.contains_region_var(rset) + } } } } -impl std::fmt::Display for Region -where - Rid: std::fmt::Display, -{ +impl std::fmt::Display for Region { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match self { Region::Static => write!(f, "'static"), Region::Var(id) => write!(f, "'_{id}"), + Region::Erased => write!(f, "'_"), + Region::Unknown => write!(f, "'_UNKNOWN_"), } } } -impl std::fmt::Display for ErasedRegion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "'_") - } -} - -pub struct IncompleteFormatter<'a> { - def: &'a TypeDecl, -} - -impl<'a> Formatter for IncompleteFormatter<'a> { - fn format_object(&self, id: TypeVarId::Id) -> String { - self.def.format_object(id) - } -} - -impl<'a> Formatter for IncompleteFormatter<'a> { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - id.to_pretty_string() - } -} - -impl<'a, 'b, Rid: Copy + Eq> Formatter<&'b Region> for IncompleteFormatter<'a> -where - TypeDecl: Formatter<&'b Region>, -{ - fn format_object(&self, r: &'b Region) -> String { - self.def.format_object(r) - } -} - -impl<'a, 'b> Formatter<&'b ErasedRegion> for IncompleteFormatter<'a> { - fn format_object(&self, r: &'b ErasedRegion) -> String { - self.def.format_object(r) - } -} - -impl<'a> Formatter for IncompleteFormatter<'a> { - fn format_object(&self, id: RegionVarId::Id) -> String { - self.def.format_object(id) - } -} - -impl<'a> Formatter for IncompleteFormatter<'a> { - fn format_object(&self, id: TypeDeclId::Id) -> String { - // For type def ids, we simply print the def id because - // we lack context - id.to_pretty_string() - } -} - -impl<'a> Formatter for IncompleteFormatter<'a> { - fn format_object(&self, id: ConstGenericVarId::Id) -> String { - self.def.format_object(id) - } -} - -pub struct DummyFormatter {} - -impl Formatter for DummyFormatter { - fn format_object(&self, id: TypeVarId::Id) -> String { - id.to_pretty_string() - } -} - -impl Formatter<&Region> for DummyFormatter -where - DummyFormatter: Formatter, -{ - fn format_object(&self, r: &Region) -> String { - r.fmt_with_ctx(self) - } -} - -impl Formatter<&ErasedRegion> for DummyFormatter { - fn format_object(&self, _: &ErasedRegion) -> String { - "'_".to_string() - } -} - -impl Formatter for DummyFormatter { - fn format_object(&self, id: RegionVarId::Id) -> String { - id.to_pretty_string() - } -} - -impl Formatter for DummyFormatter { - fn format_object(&self, id: TypeDeclId::Id) -> String { - id.to_pretty_string() +impl std::string::ToString for Ty { + fn to_string(&self) -> String { + self.fmt_with_ctx(&DummyFormatter {}) } } -impl Formatter for DummyFormatter { - fn format_object(&self, id: ConstGenericVarId::Id) -> String { - id.to_pretty_string() +impl TraitRef { + pub fn substitute( + &self, + rsubst: &dyn Fn(&Region) -> Region, + tsubst: &dyn Fn(&TypeVarId::Id) -> Ty, + cgsubst: &dyn Fn(&ConstGenericVarId::Id) -> ConstGeneric, + ) -> TraitRef { + let generics = self.generics.substitute(rsubst, tsubst, cgsubst); + let trait_decl_ref = self.trait_decl_ref.substitute(rsubst, tsubst, cgsubst); + TraitRef { + trait_id: self.trait_id.clone(), + generics, + trait_decl_ref, + } } } -impl Formatter for DummyFormatter { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - id.to_pretty_string() +impl TraitDeclRef { + pub fn substitute( + &self, + rsubst: &dyn Fn(&Region) -> Region, + tsubst: &dyn Fn(&TypeVarId::Id) -> Ty, + cgsubst: &dyn Fn(&ConstGenericVarId::Id) -> ConstGeneric, + ) -> TraitDeclRef { + let generics = self.generics.substitute(rsubst, tsubst, cgsubst); + TraitDeclRef { + trait_id: self.trait_id, + generics, + } } } -impl std::string::ToString for Ty { - fn to_string(&self) -> String { - self.fmt_with_ctx(&DummyFormatter {}) +impl GenericArgs { + pub fn substitute( + &self, + rsubst: &dyn Fn(&Region) -> Region, + tsubst: &dyn Fn(&TypeVarId::Id) -> Ty, + cgsubst: &dyn Fn(&ConstGenericVarId::Id) -> ConstGeneric, + ) -> GenericArgs { + let GenericArgs { + regions, + types, + const_generics, + trait_refs, + } = self; + let regions = Ty::substitute_regions(regions, rsubst); + let types = types + .iter() + .map(|ty| ty.substitute(rsubst, tsubst, cgsubst)) + .collect(); + let const_generics = const_generics + .iter() + .map(|cg| cg.substitute(cgsubst)) + .collect(); + let trait_refs = trait_refs + .iter() + .map(|x| x.substitute(rsubst, tsubst, cgsubst)) + .collect(); + GenericArgs { + regions, + types, + const_generics, + trait_refs, + } } } -// TODO: mixing Copy and Clone in the trait requirements below. Update to only use Copy. -impl Ty -where - R: Copy + Clone + Eq, -{ - pub fn substitute( +impl Ty { + pub fn substitute( &self, - rsubst: &dyn Fn(&R) -> R1, - tsubst: &dyn Fn(&TypeVarId::Id) -> Ty, + rsubst: &dyn Fn(&Region) -> Region, + tsubst: &dyn Fn(&TypeVarId::Id) -> Ty, cgsubst: &dyn Fn(&ConstGenericVarId::Id) -> ConstGeneric, - ) -> Ty - where - R1: Clone + Eq, - { + ) -> Ty { match self { - Ty::Adt(id, regions, tys, cgs) => { - let nregions = Ty::substitute_regions(regions, rsubst); - let ntys = tys - .iter() - .map(|ty| ty.substitute(rsubst, tsubst, cgsubst)) - .collect(); - let ncgs = cgs.iter().map(|cg| cg.substitute(cgsubst)).collect(); - Ty::Adt(id.clone(), nregions, ntys, ncgs) + Ty::Adt(id, args) => { + let args = args.substitute(rsubst, tsubst, cgsubst); + Ty::Adt(*id, args) } Ty::TypeVar(id) => tsubst(id), Ty::Literal(pty) => Ty::Literal(*pty), @@ -821,84 +1106,99 @@ where Ty::RawPtr(ty, kind) => { Ty::RawPtr(Box::new(ty.substitute(rsubst, tsubst, cgsubst)), *kind) } + Ty::TraitType(trait_ref, args, name) => { + let trait_ref = trait_ref.substitute(rsubst, tsubst, cgsubst); + let args = args.substitute(rsubst, tsubst, cgsubst); + Ty::TraitType(trait_ref, args, name.clone()) + } + Ty::Arrow(inputs, box output) => { + let inputs = inputs + .iter() + .map(|ty| ty.substitute(rsubst, tsubst, cgsubst)) + .collect(); + let output = output.substitute(rsubst, tsubst, cgsubst); + Ty::Arrow(inputs, Box::new(output)) + } } } - fn substitute_regions(regions: &Vec, rsubst: &dyn Fn(&R) -> R1) -> Vec - where - R1: Clone + Eq, - { + fn substitute_regions(regions: &[Region], rsubst: &dyn Fn(&Region) -> Region) -> Vec { Vec::from_iter(regions.iter().map(|rid| rsubst(rid))) } /// Substitute the type parameters // TODO: tsubst and cgsubst should be closures instead of hashmaps - pub fn substitute_types(&self, subst: &TypeSubst, cgsubst: &ConstGenericSubst) -> Self { + pub fn substitute_types(&self, subst: &TypeSubst, cgsubst: &ConstGenericSubst) -> Self { self.substitute(&|r| *r, &|tid| subst.get(tid).unwrap().clone(), &|cgid| { cgsubst.get(cgid).unwrap().clone() }) } /// Erase the regions - pub fn erase_regions(&self) -> ETy { - self.substitute( - &|_| ErasedRegion::Erased, - &|tid| Ty::TypeVar(*tid), - &|cgid| ConstGeneric::Var(*cgid), - ) + pub fn erase_regions(&self) -> Ty { + self.substitute(&|_| Region::Erased, &|tid| Ty::TypeVar(*tid), &|cgid| { + ConstGeneric::Var(*cgid) + }) } /// Erase the regions and substitute the types at the same time pub fn erase_regions_substitute_types( &self, - subst: &TypeSubst, + subst: &TypeSubst, cgsubst: &ConstGenericSubst, - ) -> ETy { + ) -> Ty { self.substitute( - &|_| ErasedRegion::Erased, + &|_| Region::Erased, &|tid| subst.get(tid).unwrap().clone(), &|cgid| cgsubst.get(cgid).unwrap().clone(), ) } /// Returns `true` if the type contains some region or type variables + /// TODO: reimplement this with visitors pub fn contains_variables(&self) -> bool { match self { Ty::TypeVar(_) => true, Ty::Literal(_) | Ty::Never => false, Ty::Ref(_, _, _) => true, // Always contains a region identifier Ty::RawPtr(ty, _) => ty.contains_variables(), - Ty::Adt(_, regions, tys, _) => { - !regions.is_empty() || tys.iter().any(|x| x.contains_variables()) + Ty::TraitType(_, args, _) | Ty::Adt(_, args) => { + // For the trait type case: we are checking the projected type, + // so we don't need to explore the trait ref + !args.regions.is_empty() || args.types.iter().any(|x| x.contains_variables()) + } + Ty::Arrow(inputs, box output) => { + inputs.iter().any(|ty| ty.contains_variables()) || output.contains_variables() } } } /// Returns `true` if the type contains some regions + /// TODO: reimplement this with visitors pub fn contains_regions(&self) -> bool { match self { Ty::TypeVar(_) => false, Ty::Literal(_) | Ty::Never => false, Ty::Ref(_, _, _) => true, Ty::RawPtr(ty, _) => ty.contains_regions(), - Ty::Adt(_, regions, tys, _) => { - !regions.is_empty() || tys.iter().any(|x| x.contains_regions()) + Ty::TraitType(_, args, _) | Ty::Adt(_, args) => { + // For the trait type case: we are checking the projected type, + // so we don't need to explore the trait ref + !args.regions.is_empty() || args.types.iter().any(|x| x.contains_regions()) + } + Ty::Arrow(inputs, box output) => { + inputs.iter().any(|ty| ty.contains_regions()) || output.contains_regions() } } } } -// TODO: mixing Copy and Clone in the trait requirements below. Update to only use Copy. -impl RTy { +impl Ty { /// Substitute the regions and type parameters - pub fn substitute_regions_types( - &self, - rsubst: &RegionSubst>, - tsubst: &TypeSubst>, - ) -> Self { + pub fn substitute_regions_types(&self, rsubst: &RegionSubst, tsubst: &TypeSubst) -> Self { self.substitute( - &|rid| match rid { - Region::Static => Region::Static, + &|r| match r { + Region::Static | Region::Erased | Region::Unknown => *r, Region::Var(rid) => *rsubst.get(rid).unwrap(), }, &|tid| tsubst.get(tid).unwrap().clone(), @@ -931,33 +1231,17 @@ where res } -pub fn make_type_subst< - 'a, - R: 'a + Eq, - I1: Iterator, - I2: Iterator>, ->( +pub fn make_type_subst<'a, I1: Iterator, I2: Iterator>( params: I1, types: I2, -) -> TypeSubst -where - R: Clone, -{ +) -> TypeSubst { make_subst(params, types) } -pub fn make_region_subst< - 'a, - R: 'a + Eq, - I1: Iterator, - I2: Iterator, ->( +pub fn make_region_subst<'a, I1: Iterator, I2: Iterator>( keys: I1, values: I2, -) -> RegionSubst -where - R: Clone, -{ +) -> RegionSubst { make_subst(keys, values) } @@ -974,58 +1258,237 @@ pub fn make_cg_subst< impl Formatter for TypeDecl { fn format_object(&self, id: TypeVarId::Id) -> String { - let var = self.type_params.get(id).unwrap(); + let var = self.generics.types.get(id).unwrap(); var.to_string() } } -impl Formatter for TypeDecl { - fn format_object(&self, id: RegionVarId::Id) -> String { - let var = self.region_params.get(id).unwrap(); +impl Formatter for TypeDecl { + fn format_object(&self, id: RegionId::Id) -> String { + let var = self.generics.regions.get(id).unwrap(); var.to_string() } } impl Formatter for TypeDecl { fn format_object(&self, id: ConstGenericVarId::Id) -> String { - let var = self.const_generic_params.get(id).unwrap(); + let var = self.generics.const_generics.get(id).unwrap(); var.to_string() } } -impl Formatter<&Region> for TypeDecl +impl Formatter<&Region> for TypeDecl where - TypeDecl: Formatter, + TypeDecl: Formatter, { - fn format_object(&self, r: &Region) -> String { + fn format_object(&self, r: &Region) -> String { r.fmt_with_ctx(self) } } -impl Formatter<&ErasedRegion> for TypeDecl { - fn format_object(&self, _: &ErasedRegion) -> String { - "'_".to_string() +impl Ty { + // TODO: reimplement this with visitors + pub fn contains_never(&self) -> bool { + match self { + Ty::Never => true, + Ty::TraitType(_, args, _) | Ty::Adt(_, args) => { + // For the trait type case: we are checking the projected type, + // so we don't need to explore the trait ref + args.types.iter().any(|ty| ty.contains_never()) + } + Ty::TypeVar(_) | Ty::Literal(_) => false, + Ty::Ref(_, ty, _) | Ty::RawPtr(ty, _) => ty.contains_never(), + Ty::Arrow(inputs, box output) => { + inputs.iter().any(|ty| ty.contains_never()) || output.contains_never() + } + } } } -impl Formatter for TypeDecls { - fn format_object(&self, id: TypeDeclId::Id) -> String { - // The definition may not be available yet, especially if we print-debug - // while translating the crate - match self.get(id) { - Option::None => id.to_pretty_string(), - Option::Some(def) => def.name.to_string(), +pub struct TySubst { + pub ignore_regions: bool, + /// This map is from regions to regions, not from region ids to regions. + /// In case the regions are not erased, we must be careful with the + /// static region. + pub regions_map: HashMap, + pub type_vars_map: HashMap, + pub const_generics_map: HashMap, +} + +macro_rules! check_ok_return { + ( $x:expr ) => {{ + if $x { + return Ok(()); + } else { + return Err(()); + } + }}; +} + +macro_rules! check_ok { + ( $x:expr ) => {{ + if !$x { + return Err(()); + } + }}; +} + +impl TySubst { + fn new() -> Self { + let mut regions_map = HashMap::new(); + // Fix the static and erased regions + regions_map.insert(Region::Static, Region::Static); + regions_map.insert(Region::Erased, Region::Erased); + TySubst { + ignore_regions: false, + regions_map, + type_vars_map: HashMap::new(), + const_generics_map: HashMap::new(), + } + } + + fn unify_regions(&mut self, src: &Region, tgt: &Region) -> Result<(), ()> { + use Result::*; + match self.regions_map.get(src) { + None => { + check_ok_return!(self.regions_map.insert(*src, *tgt).is_none()); + } + Some(src) => { + check_ok_return!(src == tgt); + } + } + } + + fn unify_const_generics(&mut self, src: &ConstGeneric, tgt: &ConstGeneric) -> Result<(), ()> { + use ConstGeneric::*; + use Result::*; + if let Var(v) = src { + check_ok_return!(self.const_generics_map.insert(*v, tgt.clone()).is_none()); + } + match (src, tgt) { + (Global(src), Global(tgt)) => { + check_ok_return!(src == tgt); + } + (Value(src), Value(tgt)) => { + check_ok_return!(src == tgt); + } + _ => Err(()), } } + + fn unify_types(&mut self, src: &Ty, tgt: &Ty) -> Result<(), ()> { + use Result::*; + use Ty::*; + + if let TypeVar(v) = src { + check_ok_return!(self.type_vars_map.insert(*v, tgt.clone()).is_none()); + } + + match (src, tgt) { + (Adt(src_id, src_args), Adt(tgt_id, tgt_args)) => { + check_ok!(src_id == tgt_id); + self.unify_args(src_args, tgt_args) + } + (Literal(src), Literal(tgt)) => { + check_ok_return!(src == tgt); + } + (Never, Never) => Ok(()), + (Ref(src_r, box src_ty, src_kind), Ref(tgt_r, box tgt_ty, tgt_kind)) => { + if !self.ignore_regions { + self.unify_regions(src_r, tgt_r)?; + } + self.unify_types(src_ty, tgt_ty)?; + check_ok_return!(src_kind == tgt_kind); + } + (RawPtr(box src_ty, src_kind), RawPtr(box tgt_ty, tgt_kind)) => { + self.unify_types(src_ty, tgt_ty)?; + check_ok_return!(src_kind == tgt_kind); + } + _ => Err(()), + } + } + + fn unify_regions_lists(&mut self, src: &[Region], tgt: &[Region]) -> Result<(), ()> { + check_ok!(src.len() == tgt.len()); + for (src, tgt) in src.iter().zip(tgt.iter()) { + self.unify_regions(src, tgt)?; + } + Ok(()) + } + + fn unify_const_generics_lists( + &mut self, + src: &[ConstGeneric], + tgt: &[ConstGeneric], + ) -> Result<(), ()> { + check_ok!(src.len() == tgt.len()); + for (src, tgt) in src.iter().zip(tgt.iter()) { + self.unify_const_generics(src, tgt)?; + } + Ok(()) + } + + fn unify_types_lists(&mut self, src: &[Ty], tgt: &[Ty]) -> Result<(), ()> { + check_ok!(src.len() == tgt.len()); + for (src, tgt) in src.iter().zip(tgt.iter()) { + self.unify_types(src, tgt)?; + } + Ok(()) + } + + fn unify_args( + &mut self, + src: &crate::gast::GenericArgs, + tgt: &crate::gast::GenericArgs, + ) -> Result<(), ()> { + if !self.ignore_regions { + self.unify_regions_lists(&src.regions, &tgt.regions)?; + } + self.unify_types_lists(&src.types, &tgt.types)?; + self.unify_const_generics_lists(&src.const_generics, &tgt.const_generics)?; + Ok(()) + } } -impl Ty { - pub fn contains_never(&self) -> bool { - match self { - Ty::Never => true, - Ty::Adt(_, _, tys, _) => tys.iter().any(|ty| ty.contains_never()), - Ty::TypeVar(_) | Ty::Literal(_) => false, - Ty::Ref(_, ty, _) | Ty::RawPtr(ty, _) => ty.contains_never(), +impl TySubst { + #[allow(clippy::result_unit_err)] + pub fn unify_args_with_fixed( + fixed_type_vars: impl std::iter::Iterator, + fixed_const_generic_vars: impl std::iter::Iterator, + src: &crate::gast::GenericArgs, + tgt: &crate::gast::GenericArgs, + ) -> Result { + let mut s = TySubst::new(); + for v in fixed_type_vars { + s.type_vars_map.insert(v, Ty::TypeVar(v)); + } + for v in fixed_const_generic_vars { + s.const_generics_map.insert(v, ConstGeneric::Var(v)); + } + + s.ignore_regions = true; + s.unify_args(src, tgt)?; + Ok(s) + } +} + +/// Visitor to replace the [TraitInstanceId::SelfId] inside a type +struct TraitInstanceIdSelfReplacer { + new_id: TraitInstanceId, +} + +impl MutTypeVisitor for TraitInstanceIdSelfReplacer { + fn visit_trait_instance_id(&mut self, id: &mut TraitInstanceId) { + match id { + TraitInstanceId::SelfId => *id = self.new_id.clone(), + TraitInstanceId::ParentClause(box id, _, _) + | TraitInstanceId::ItemClause(box id, _, _, _) => self.visit_trait_instance_id(id), + TraitInstanceId::TraitImpl(_) + | TraitInstanceId::Clause(_) + | TraitInstanceId::BuiltinOrAuto(_) + | TraitInstanceId::FnPointer(_) + | TraitInstanceId::Unsolved(..) + | TraitInstanceId::Unknown(_) => (), } } } @@ -1038,37 +1501,55 @@ make_generic_in_borrows! { // TODO: we should use traits with default implementations to allow overriding // the default behavior (that would also prevent problems with naming collisions) pub trait TypeVisitor { - fn visit_ty(&mut self, ty: &Ty) { + fn visit_ty(&mut self, ty: &Ty) { self.default_visit_ty(ty) } - fn default_visit_ty(&mut self, ty: &Ty) { + fn default_visit_ty(&mut self, ty: &Ty) { use Ty::*; match ty { - Adt(id, rl, tys, cgs) => self.visit_ty_adt(id, rl, tys, cgs), + Adt(id, args) => self.visit_ty_adt(id, args), TypeVar(vid) => self.visit_ty_type_var(vid), Literal(lit) => self.visit_ty_literal(lit), Never => self.visit_ty_never(), Ref(r, ty, rk) => self.visit_ty_ref(r, ty, rk), RawPtr(ty, rk) => self.visit_ty_raw_ptr(ty, rk), + TraitType(trait_ref, generics, _name) => { + self.visit_trait_ref(trait_ref); + self.visit_generic_args(generics); + } + Arrow(inputs, box output) => self.visit_arrow(inputs, output), + } + } + + fn visit_region(&mut self, r: &Region) { + match r { + Region::Erased | Region::Static | Region::Unknown => (), + Region::Var(id) => self.visit_region_id(id), + } + } + + fn visit_region_id(&mut self, _ : &RegionId::Id) {} + + fn visit_arrow(&mut self, inputs: &Vec, output: &Ty) { + for ty in inputs { + self.visit_ty(ty); } + self.visit_ty(output); } - fn visit_ty_adt( + fn visit_ty_adt( &mut self, id: &TypeId, - rl: &Vec, - tys: &Vec>, - cgs: &Vec, + args: &GenericArgs, ) { self.visit_type_id(id); - // We ignore the regions - for ty in tys { - self.visit_ty(ty) - } - for cg in cgs { - self.visit_const_generic(cg); - } + self.visit_generic_args(args); + } + + fn visit_region_var(&mut self, r: &RegionVar) { + // Ignoring the name + self.visit_region_id(&r.index); } fn visit_ty_type_var(&mut self, vid: &TypeVarId::Id) { @@ -1079,13 +1560,12 @@ pub trait TypeVisitor { fn visit_ty_never(&mut self) {} - fn visit_ty_ref(&mut self, _r: &R, ty: &Box>, _rk: &RefKind) { - // We ignore the region + fn visit_ty_ref(&mut self, r: &Region, ty: &Box, _rk: &RefKind) { + self.visit_region(r); self.visit_ty(ty); } - fn visit_ty_raw_ptr(&mut self, ty: &Box>, _rk: &RefKind) { - // We ignore the region + fn visit_ty_raw_ptr(&mut self, ty: &Box, _rk: &RefKind) { self.visit_ty(ty); } @@ -1111,11 +1591,211 @@ pub trait TypeVisitor { } } + fn visit_type_var(&mut self, ty: &TypeVar) { + self.visit_type_var_id(&ty.index); + // Ignoring the name + } + + fn visit_const_generic_var(&mut self, cg: &ConstGenericVar) { + self.visit_const_generic_var_id(&cg.index); + // Ignoring the name and type + } + fn visit_global_decl_id(&mut self, _: &GlobalDeclId::Id) {} fn visit_type_var_id(&mut self, _: &TypeVarId::Id) {} fn visit_const_generic_var_id(&mut self, _: &ConstGenericVarId::Id) {} fn visit_literal(&mut self, _: &Literal) {} + + fn visit_trait_ref(&mut self, tr: &TraitRef) { + let TraitRef { + trait_id, + generics, + trait_decl_ref, + } = tr; + self.visit_trait_instance_id(trait_id); + self.visit_generic_args(generics); + self.visit_trait_decl_ref(trait_decl_ref); + } + + fn visit_trait_decl_ref(&mut self, tr: &TraitDeclRef) { + let TraitDeclRef { + trait_id, + generics, + } = tr; + self.visit_trait_decl_id(trait_id); + self.visit_generic_args(generics); + } + + fn visit_trait_decl_id(&mut self, _: &TraitDeclId::Id) {} + fn visit_trait_impl_id(&mut self, _: &TraitImplId::Id) {} + fn visit_trait_clause_id(&mut self, _: &TraitClauseId::Id) {} + + fn default_visit_trait_instance_id(&mut self, id: &TraitInstanceId) { + match id { + TraitInstanceId::SelfId => (), + TraitInstanceId::TraitImpl(id) => self.visit_trait_impl_id(id), + TraitInstanceId::BuiltinOrAuto(id) => self.visit_trait_decl_id(id), + TraitInstanceId::Clause(id) => self.visit_trait_clause_id(id), + TraitInstanceId::ParentClause(box id, decl_id, clause_id) => { + self.visit_trait_instance_id(id); + self.visit_trait_decl_id(decl_id); + self.visit_trait_clause_id(clause_id) + }, + TraitInstanceId::ItemClause(box id, decl_id, _name, clause_id) => { + self.visit_trait_instance_id(id); + self.visit_trait_decl_id(decl_id); + self.visit_trait_clause_id(clause_id) + }, + TraitInstanceId::FnPointer(box ty) => { + self.visit_ty(ty); + } + TraitInstanceId::Unsolved(trait_id, generics) => { + self.visit_trait_decl_id(trait_id); + self.visit_generic_args(generics); + }, + TraitInstanceId::Unknown(_) => (), + } + } + + fn visit_trait_instance_id(&mut self, id: &TraitInstanceId) { + self.default_visit_trait_instance_id(id) + } + + fn visit_generic_args(&mut self, g: &GenericArgs) { + for r in &g.regions { + self.visit_region(r) + } + for t in &g.types { + self.visit_ty(t); + } + for cg in &g.const_generics { + self.visit_const_generic(cg); + } + for t in &g.trait_refs { + self.visit_trait_ref(t); + } + } + + fn visit_generic_params(&mut self, g: &GenericParams) { + for r in g.regions.iter() { + self.visit_region_var(r) + } + for t in g.types.iter() { + self.visit_type_var(t); + } + for cg in g.const_generics.iter() { + self.visit_const_generic_var(cg); + } + for t in g.trait_clauses.iter() { + self.visit_trait_clause(t); + } + } + + fn visit_trait_clause(&mut self, c: &TraitClause) { + let TraitClause { clause_id, meta: _, trait_id, generics } = c; + self.visit_trait_clause_id(clause_id); + self.visit_trait_decl_id(trait_id); + self.visit_generic_args(generics); + } + + fn visit_predicates(&mut self, preds: &Predicates) { + let Predicates { + regions_outlive, + types_outlive, + trait_type_constraints, + } = preds; + for p in regions_outlive { + self.visit_region(&p.0); + self.visit_region(&p.1); + } + for p in types_outlive { + self.visit_ty(&p.0); + self.visit_region(&p.1); + } + for TraitTypeConstraint { + trait_ref, + generics, + type_name: _, + ty, + } in trait_type_constraints + { + self.visit_trait_ref(trait_ref); + self.visit_generic_args(generics); + self.visit_ty(ty); + } + } + + fn visit_fun_sig(&mut self, sig: &FunSig) { + let FunSig { + is_unsafe : _, + generics, + preds, + parent_params_info: _, + inputs, + output, + } = sig; + + self.visit_generic_params(generics); + self.visit_predicates(preds); + for ty in inputs { self.visit_ty(ty); } + self.visit_ty(output); + } + + fn visit_type_outlives(&mut self, x: &TypeOutlives) { + self.visit_ty(&x.0); + } + + fn visit_trait_type_constraint(&mut self, x : &TraitTypeConstraint) { + let TraitTypeConstraint { trait_ref, generics, type_name: _, ty } = x; + self.visit_trait_ref(trait_ref); + self.visit_generic_args(generics); + self.visit_ty(ty); + } } } // make_generic_in_borrows + +impl FunSig { + pub fn fmt_with_ctx(&self, ctx: &T) -> String + where + T: TypeFormatter, + { + // Unsafe keyword + let unsafe_kw = if self.is_unsafe { + "unsafe ".to_string() + } else { + "".to_string() + }; + + // Generic parameters + let (params, trait_clauses) = self.generics.fmt_with_ctx_with_trait_clauses(ctx); + + // Arguments + let mut args: Vec = Vec::new(); + for ty in &self.inputs { + args.push(ty.fmt_with_ctx(ctx).to_string()); + } + let args = args.join(", "); + + // Return type + let ret_ty = &self.output; + let ret_ty = if ret_ty.is_unit() { + "".to_string() + } else { + format!(" -> {}", ret_ty.fmt_with_ctx(ctx)) + }; + + // Clauses + let clauses = fmt_where_clauses_with_ctx( + ctx, + "", + &self.parent_params_info, + trait_clauses, + &self.preds, + ); + + // Put everything together + format!("{unsafe_kw}fn{params}({args}){ret_ty}{clauses}",) + } +} diff --git a/charon/src/ullbc_ast.rs b/charon/src/ullbc_ast.rs index 7168bf52..3b716a74 100644 --- a/charon/src/ullbc_ast.rs +++ b/charon/src/ullbc_ast.rs @@ -1,8 +1,5 @@ //! "Unstructured LLBC" ast (ULLBC). This is LLBC before the control-flow //! reconstruction. In effect, this is a cleaned up version of MIR. -#![allow(dead_code)] - -use crate::expressions::*; pub use crate::gast::*; use crate::meta::Meta; pub use crate::types::GlobalDeclId; @@ -27,6 +24,9 @@ pub type FunDecls = FunDeclId::Map; pub type GlobalDecl = GGlobalDecl>; pub type GlobalDecls = GlobalDeclId::Map; +pub type TraitDecls = TraitDeclId::Map; +pub type TraitImpls = TraitImplId::Map; + /// A raw statement: a statement without meta data. #[derive(Debug, Clone, EnumIsA, EnumAsGetters, VariantName, Serialize)] pub enum RawStatement { diff --git a/charon/src/ullbc_ast_utils.rs b/charon/src/ullbc_ast_utils.rs index e4c640e2..d72092ef 100644 --- a/charon/src/ullbc_ast_utils.rs +++ b/charon/src/ullbc_ast_utils.rs @@ -1,15 +1,11 @@ //! Implementations for [crate::ullbc_ast] -#![allow(dead_code)] - -use crate::expressions::*; -use crate::formatter::Formatter; +use crate::common::TAB_INCR; pub use crate::gast_utils::*; use crate::meta::Meta; use crate::types::*; use crate::ullbc_ast::*; use crate::values::*; use macros::make_generic_in_borrows; -use std::iter::FromIterator; use take_mut::take; impl SwitchTargets { @@ -30,7 +26,7 @@ impl SwitchTargets { } /// Perform a type substitution - actually simply clone the object - pub fn substitute(&self, _subst: &ETypeSubst) -> Self { + pub fn substitute(&self, _subst: &TypeSubst) -> Self { self.clone() } } @@ -41,7 +37,7 @@ impl Statement { } /// Substitute the type variables and return the resulting statement. - pub fn substitute(&self, subst: &ETypeSubst) -> Statement { + pub fn substitute(&self, subst: &TypeSubst) -> Statement { let st = match &self.content { RawStatement::Assign(place, rvalue) => { RawStatement::Assign(place.substitute(subst), rvalue.substitute(subst)) @@ -62,92 +58,12 @@ impl Terminator { pub fn new(meta: Meta, content: RawTerminator) -> Self { Terminator { meta, content } } - - /// Substitute the type variables and return the resulting terminator - pub fn substitute(&self, subst: &ETypeSubst, cgsubst: &ConstGenericSubst) -> Terminator { - let terminator = match &self.content { - RawTerminator::Goto { target } => RawTerminator::Goto { target: *target }, - RawTerminator::Switch { discr, targets } => RawTerminator::Switch { - discr: discr.substitute(subst), - targets: targets.substitute(subst), - }, - RawTerminator::Panic => RawTerminator::Panic, - RawTerminator::Return => RawTerminator::Return, - RawTerminator::Unreachable => RawTerminator::Unreachable, - RawTerminator::Drop { place, target } => RawTerminator::Drop { - place: place.substitute(subst), - target: *target, - }, - RawTerminator::Call { call, target } => { - let Call { - func, - region_args, - type_args, - const_generic_args, - args, - dest, - } = call; - let call = Call { - func: func.clone(), - region_args: region_args.clone(), - type_args: type_args - .iter() - .map(|ty| ty.substitute_types(subst, cgsubst)) - .collect(), - const_generic_args: const_generic_args - .iter() - .map(|cg| cg.substitute(&|var| cgsubst.get(var).unwrap().clone())) - .collect(), - args: Vec::from_iter(args.iter().map(|arg| arg.substitute(subst))), - dest: dest.substitute(subst), - }; - RawTerminator::Call { - call, - target: *target, - } - } - RawTerminator::Assert { - cond, - expected, - target, - } => RawTerminator::Assert { - cond: cond.substitute(subst), - expected: *expected, - target: *target, - }, - }; - - Terminator::new(self.meta, terminator) - } -} - -impl BlockData { - /// Substitute the type variables and return the resulting `BlockData` - pub fn substitute(&self, subst: &ETypeSubst, cgsubst: &ConstGenericSubst) -> BlockData { - let statements = self - .statements - .iter() - .map(|x| x.substitute(subst)) - .collect(); - let terminator = self.terminator.substitute(subst, cgsubst); - BlockData { - statements, - terminator, - } - } } impl Statement { - pub fn fmt_with_ctx<'a, T>(&'a self, ctx: &T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)> - + Formatter - + Formatter - + Formatter<&'a ErasedRegion>, + T: ExprFormatter, { match &self.content { RawStatement::Assign(place, rvalue) => format!( @@ -174,17 +90,9 @@ impl Statement { } impl Terminator { - pub fn fmt_with_ctx<'a, 'b, T>(&'a self, ctx: &'b T) -> String + pub fn fmt_with_ctx(&self, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + T: ExprFormatter, { match &self.content { RawTerminator::Goto { target } => format!("goto bb{target}"), @@ -213,17 +121,8 @@ impl Terminator { format!("drop {} -> bb{}", place.fmt_with_ctx(ctx), target) } RawTerminator::Call { call, target } => { - let Call { - func, - region_args, - type_args, - const_generic_args, - args, - dest, - } = call; - let call = fmt_call(ctx, func, region_args, type_args, const_generic_args, args); - - format!("{} := {} -> bb{}", dest.fmt_with_ctx(ctx), call, target,) + let (call_s, _) = fmt_call(ctx, call); + format!("{} := {call_s} -> bb{target}", call.dest.fmt_with_ctx(ctx),) } RawTerminator::Assert { cond, @@ -240,17 +139,9 @@ impl Terminator { } impl BlockData { - pub fn fmt_with_ctx<'a, 'b, 'c, T>(&'a self, tab: &'b str, ctx: &'c T) -> String + pub fn fmt_with_ctx(&self, tab: &str, ctx: &T) -> String where - T: Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + T: ExprFormatter, { let mut out: Vec = Vec::new(); @@ -267,21 +158,13 @@ impl BlockData { } } -fn fmt_body_blocks_with_ctx<'a, 'b, 'c, C>( - body: &'a BlockId::Vector, - tab: &'b str, - ctx: &'c C, +pub(crate) fn fmt_body_blocks_with_ctx( + body: &BlockId::Vector, + tab: &str, + ctx: &C, ) -> String where - C: Formatter - + Formatter - + Formatter<&'a ErasedRegion> - + Formatter - + Formatter - + Formatter - + Formatter - + Formatter<(TypeDeclId::Id, VariantId::Id)> - + Formatter<(TypeDeclId::Id, Option, FieldId::Id)>, + C: ExprFormatter, { let block_tab = format!("{tab}{TAB_INCR}"); let mut blocks: Vec = Vec::new(); @@ -299,209 +182,21 @@ where blocks.join("\n") } -impl ExprBody { - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let locals = Some(&self.locals); - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - let ctx = GAstFormatter::new(ty_ctx, &fun_ctx, &global_ctx, None, locals, None); - self.fmt_with_ctx(TAB_INCR, &ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let locals = Some(&self.locals); - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - let ctx = GAstFormatter::new(ty_ctx, &fun_ctx, &global_ctx, None, locals, None); - self.fmt_with_ctx(TAB_INCR, &ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) - } -} - -pub(crate) struct FunDeclsFormatter<'ctx> { - decls: &'ctx FunDecls, -} - -pub(crate) struct GlobalDeclsFormatter<'ctx> { - decls: &'ctx GlobalDecls, -} - -impl<'ctx, FD, GD> Formatter<&Statement> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, -{ - fn format_object(&self, statement: &Statement) -> String { - statement.fmt_with_ctx(self) - } -} - -impl<'ctx, FD, GD> Formatter<&BlockId::Vector> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, - Self: Formatter, -{ - fn format_object(&self, body: &BlockId::Vector) -> String { - fmt_body_blocks_with_ctx(body, TAB_INCR, self) - } -} - -impl<'ctx, FD, GD> Formatter<&Terminator> for GAstFormatter<'ctx, FD, GD> -where - Self: Formatter, - Self: Formatter, -{ - fn format_object(&self, terminator: &Terminator) -> String { - terminator.fmt_with_ctx(self) - } -} - -impl<'ctx> FunDeclsFormatter<'ctx> { - pub fn new(decls: &'ctx FunDecls) -> Self { - FunDeclsFormatter { decls } - } -} - -impl<'ctx> Formatter for FunDeclsFormatter<'ctx> { - fn format_object(&self, id: FunDeclId::Id) -> String { - let d = self.decls.get(id).unwrap(); - d.name.to_string() - } -} - -impl<'ctx> GlobalDeclsFormatter<'ctx> { - pub fn new(decls: &'ctx GlobalDecls) -> Self { - GlobalDeclsFormatter { decls } - } -} - -impl<'ctx> Formatter for GlobalDeclsFormatter<'ctx> { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - let d = self.decls.get(id).unwrap(); - d.name.to_string() - } -} - -impl Formatter for GlobalDecls { - fn format_object(&self, id: GlobalDeclId::Id) -> String { - let d = self.get(id).unwrap(); - d.name.to_string() - } -} - impl FunDecl { - pub fn fmt_with_ctx<'ctx, FD, GD>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FD, - global_ctx: &'ctx GD, - ) -> String + pub fn fmt_with_ctx<'a, C>(&'a self, ctx: &C) -> String where - FD: Formatter, - GD: Formatter, + C: GFunDeclFormatter<'a, BlockId::Vector>, { - // Initialize the contexts - let fun_sig_ctx = FunSigFormatter { - ty_ctx, - global_ctx, - sig: &self.signature, - }; - - let locals = self.body.as_ref().map(|body| &body.locals); - let ctx = GAstFormatter::new( - ty_ctx, - fun_ctx, - global_ctx, - Some(&self.signature.type_params), - locals, - Some(&self.signature.const_generic_params), - ); - - // Use the contexts for printing - self.gfmt_with_ctx("", &fun_sig_ctx, &ctx) - } - - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) + self.gfmt_with_ctx("", ctx) } } impl GlobalDecl { - pub fn fmt_with_ctx<'ctx, FD, GD>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FD, - global_ctx: &'ctx GD, - ) -> String + pub fn fmt_with_ctx<'a, C>(&'a self, ctx: &C) -> String where - FD: Formatter, - GD: Formatter, + C: GGlobalDeclFormatter<'a, BlockId::Vector>, { - let locals = self.body.as_ref().map(|body| &body.locals); - let ctx = GAstFormatter::new(ty_ctx, fun_ctx, global_ctx, None, locals, None); - - // Use the contexts for printing - self.gfmt_with_ctx("", &ctx) - } - - pub fn fmt_with_decls<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDecls, - global_ctx: &'ctx GlobalDecls, - ) -> String { - let fun_ctx = FunDeclsFormatter::new(fun_ctx); - let global_ctx = GlobalDeclsFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_names<'ctx>( - &self, - ty_ctx: &'ctx TypeDecls, - fun_ctx: &'ctx FunDeclId::Map, - global_ctx: &'ctx GlobalDeclId::Map, - ) -> String { - let fun_ctx = FunNamesFormatter::new(fun_ctx); - let global_ctx = GlobalNamesFormatter::new(global_ctx); - self.fmt_with_ctx(ty_ctx, &fun_ctx, &global_ctx) - } - - pub fn fmt_with_ctx_names(&self, ctx: &CtxNames<'_>) -> String { - self.fmt_with_names(ctx.type_context, ctx.fun_context, ctx.global_context) + self.gfmt_with_ctx("", ctx) } } @@ -526,6 +221,9 @@ impl BlockData { f(meta, nst, op); } } + Rvalue::Repeat(op, _, _) => { + f(meta, nst, op); + } Rvalue::Global(_) | Rvalue::Discriminant(_) | Rvalue::Ref(_, _) | Rvalue::Len(..) => { // No operands: nothing to do } diff --git a/charon/src/ullbc_to_llbc.rs b/charon/src/ullbc_to_llbc.rs index 338e8511..000b58fc 100644 --- a/charon/src/ullbc_to_llbc.rs +++ b/charon/src/ullbc_to_llbc.rs @@ -21,9 +21,10 @@ //! many nodes and edges). use crate::expressions::Place; +use crate::formatter::Formatter; use crate::llbc_ast as tgt; use crate::meta::{combine_meta, Meta}; -use crate::types::TypeDecls; +use crate::translate_ctx::TransCtx; use crate::ullbc_ast::FunDeclId; use crate::ullbc_ast::{self as src, GlobalDeclId}; use crate::values as v; @@ -87,6 +88,8 @@ struct CfgPartialInfo { pub backward_edges: HashSet<(src::BlockId::Id, src::BlockId::Id)>, /// The blocks whose terminators are a switch are stored here. pub switch_blocks: HashSet, + /// The set of nodes from where we can only reach error nodes (panic, etc.) + pub only_reach_error: HashSet, } /// Similar to `CfgPartialInfo`, but with more information @@ -96,8 +99,10 @@ struct CfgInfo { pub loop_entries: HashSet, pub backward_edges: HashSet<(src::BlockId::Id, src::BlockId::Id)>, pub switch_blocks: HashSet, + pub only_reach_error: HashSet, /// The reachability matrix: /// src can reach dest <==> (src, dest) in reachability + /// /// TODO: this is not necessary anymore. There is a place where we use it /// as a test to shortcut some computations, but computing this matrix /// is actually probably too expensive for the shortcut to be useful... @@ -113,6 +118,7 @@ fn build_cfg_partial_info(body: &src::ExprBody) -> CfgPartialInfo { loop_entries: HashSet::new(), backward_edges: HashSet::new(), switch_blocks: HashSet::new(), + only_reach_error: HashSet::new(), }; // Add the nodes @@ -140,6 +146,18 @@ fn block_is_switch(body: &src::ExprBody, block_id: src::BlockId::Id) -> bool { block.terminator.content.is_switch() } +/// The terminator of the block is a panic, etc. +fn block_is_error(body: &src::ExprBody, block_id: src::BlockId::Id) -> bool { + let block = body.body.get(block_id).unwrap(); + use src::RawTerminator::*; + match &block.terminator.content { + Panic | Unreachable => true, + Goto { .. } | Switch { .. } | Return { .. } | Drop { .. } | Call { .. } | Assert { .. } => { + false + } + } +} + fn build_cfg_partial_info_edges( cfg: &mut CfgPartialInfo, ancestors: &im::HashSet, @@ -164,6 +182,7 @@ fn build_cfg_partial_info_edges( // Retrieve the block targets let targets = get_block_targets(body, block_id); + let mut has_backward_edge = false; // Add edges for all the targets and explore them, if they are not predecessors for tgt in &targets { @@ -174,6 +193,7 @@ fn build_cfg_partial_info_edges( // CFG without backward edges and exploring it if ancestors.contains(tgt) { // This is a backward edge + has_backward_edge = true; cfg.loop_entries.insert(*tgt); cfg.backward_edges.insert((block_id, *tgt)); } else { @@ -182,6 +202,18 @@ fn build_cfg_partial_info_edges( build_cfg_partial_info_edges(cfg, &ancestors, explored, body, *tgt); } } + + // Check if this node can only reach error nodes: + // - we check if the current node ends with an error terminator + // - or check that all the targets lead to error nodes + // Note that if there is a backward edge, we consider that we don't necessarily + // go to error. + if !has_backward_edge + && (block_is_error(body, block_id) + || targets.iter().all(|tgt| cfg.only_reach_error.contains(tgt))) + { + cfg.only_reach_error.insert(block_id); + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -210,14 +242,14 @@ impl PartialOrd for OrdBlockId { fn compute_reachability(cfg: &CfgPartialInfo) -> HashSet<(src::BlockId::Id, src::BlockId::Id)> { // We simply use Floyd-Warshall. // We just need to be a little careful: we have to make sure the value we - // use for infinity is never reached. That is to say, that there are stricly + // use for infinity is never reached. That is to say, that there are // less edges in the graph than usize::MAX. // Note that for now, the assertion will actually always statically succeed, // because `edge_count` returns a usize... // Also, if we have as many edges as usize::MAX, the computer will probably // be out of memory... - // It still is good to keep it there. - assert!(cfg.cfg.edge_count() < std::usize::MAX); + // It it still good to keep it there, though. + assert!(cfg.cfg.edge_count() < std::usize::MAX); // Making the comparison strict to avoid warnings... let fw_matrix: HashMap<(src::BlockId::Id, src::BlockId::Id), usize> = floyd_warshall(&cfg.cfg, &|_| 1).unwrap(); @@ -244,6 +276,7 @@ fn compute_cfg_info_from_partial(cfg: CfgPartialInfo) -> CfgInfo { loop_entries, backward_edges, switch_blocks, + only_reach_error, } = cfg; CfgInfo { @@ -252,6 +285,7 @@ fn compute_cfg_info_from_partial(cfg: CfgPartialInfo) -> CfgInfo { loop_entries, backward_edges, switch_blocks, + only_reach_error, reachability, } } @@ -624,8 +658,9 @@ fn compute_loop_exit_candidates( /// might end up being used for one of the inner loops... /// /// The best exit is the following one: -/// - it is the one which is used the most times (actually, there should be -/// at most one candidate which is referenced strictly more than once) +/// - it is the one which is used the most times (note that there can be +/// several candidates which are referenced strictly more than once: see the +/// comment below) /// - if several exits have the same number of occurrences, we choose the one /// for which we goto the "earliest" (earliest meaning that the goto is close to /// the loop entry node in the AST). The reason is that all the loops should @@ -633,6 +668,68 @@ fn compute_loop_exit_candidates( /// to the exit (note that this is not necessarily the first /// if ... then ... else ... we find: loop conditions can be arbitrary /// expressions, containing branchings). +/// +/// # Several candidates for a loop exit: +/// ===================================== +/// There used to be a sanity check to ensure there are no two different +/// candidates with exactly the same number of occurrences and distance from +/// the entry of the loop, if the number of occurrences is > 1. +/// +/// We removed it because it does happen, for instance here (the match +/// introduces an `unreachable` node, and it has the same number of +/// occurrences and the same distance to the loop entry as the `panic` +/// node): +/// +/// ```text +/// pub fn list_nth_mut_loop_pair<'a, T>( +/// mut ls: &'a mut List, +/// mut i: u32, +/// ) -> &'a mut T { +/// loop { +/// match ls { +/// List::Nil => { +/// panic!() // <-- best candidate +/// } +/// List::Cons(x, tl) => { +/// if i == 0 { +/// return x; +/// } else { +/// ls = tl; +/// i -= 1; +/// } +/// } +/// _ => { +/// // Note that Rustc always introduces an unreachable branch after +/// // desugaring matches. +/// unreachable!(), // <-- best candidate +/// } +/// } +/// } +/// } +/// ``` +/// +/// When this happens we choose an exit candidate whose edges don't necessarily +/// lead to an error (above there are none, so we don't choose any exits). Note +/// that this last condition is important to prevent loops from being unnecessarily +/// nested: +/// +/// ```text +/// pub fn nested_loops_enum(step_out: usize, step_in: usize) -> usize { +/// let mut s = 0; +/// +/// for _ in 0..128 { // We don't want this loop to be nested with the loops below +/// s += 1; +/// } +/// +/// for _ in 0..(step_out) { +/// for _ in 0..(step_in) { +/// s += 1; +/// } +/// } +/// +/// s +/// } +/// ``` fn compute_loop_exits(cfg: &CfgInfo) -> HashMap> { let mut explored = HashSet::new(); let mut ordered_loops = Vec::new(); @@ -673,11 +770,13 @@ fn compute_loop_exits(cfg: &CfgInfo) -> HashMap HashMap = None; let mut best_occurrences = 0; let mut best_dist_sum = std::usize::MAX; @@ -719,71 +820,68 @@ fn compute_loop_exits(cfg: &CfgInfo) -> HashMap = loop_exits .iter() - .filter(|(_, occs, dsum)| *occs == best_occurrences && *dsum == best_dist_sum) - .count(); - - // There used to be a sanity check to ensure there are no two different - // candidates with exactly the same number of occurrences and dist sum - // if the number of occurrences is > 1. - // - // We removed it because it does happen, for instance here (the match - // introduces an `unreachable` node, and it has the same number of - // occurrences and the same distance to the loop entry as the `panic` - // node): - // - // ``` - // pub fn list_nth_mut_loop_pair<'a, 'b, T>( - // mut ls0: &'a mut List, - // mut ls1: &'b mut List, - // mut i: u32, - // ) -> (&'a mut T, &'b mut T) { - // loop { - // match (ls0, ls1) { - // (List::Nil, _) | (_, List::Nil) => { - // panic!() - // } - // (List::Cons(x0, tl0), List::Cons(x1, tl1)) => { - // if i == 0 { - // return (x0, x1); - // } else { - // ls0 = tl0; - // ls1 = tl1; - // i -= 1; - // } - // } - // } - // } - // } - // ``` - - // If there are several best candidates, - // it is actually better not to choose any. - // - // Example: - // ======== - // ``` - // loop { - // match ls { - // List::Nil => { - // panic!() // <-- best candidate - // } - // List::Cons(x, tl) => { - // if i == 0 { - // return x; - // } else { - // ls = tl; - // i -= 1; - // } - // } - // // <-- best candidate (Rustc introduces an `unrechable` case) - // } - // } - // ``` + .filter_map(|(bid, occs, dsum)| { + if *occs == best_occurrences && *dsum == best_dist_sum { + Some(*bid) + } else { + None + } + }) + .collect(); + let num_possible_candidates = loop_exits.len(); + + // If there is exactly one one best candidate, it is easy. + // Otherwise we need to split further. if num_possible_candidates > 1 { - // We choose not to select an exit - chosen_loop_exits.insert(loop_id, None); + // TODO: if we use a lexicographic order we can merge this with the code + // above. + // Remove the candidates which only lead to errors (panic or unreachable). + let candidates: Vec<_> = possible_candidates + .iter() + .filter(|bid| !cfg.only_reach_error.contains(bid)) + .collect(); + // If there is exactly one candidate we select it + if candidates.len() == 1 { + let exit_id = *candidates[0]; + exits.insert(exit_id); + chosen_loop_exits.insert(loop_id, Some(exit_id)); + } else { + // Otherwise we do not select any exit. + // We don't want to select any exit if we are in the below situation + // (all paths lead to errors). We added a sanity check below to + // catch the situations where there are several exits which don't + // lead to errors. + // + // Example: + // ======== + // ``` + // loop { + // match ls { + // List::Nil => { + // panic!() // <-- best candidate + // } + // List::Cons(x, tl) => { + // if i == 0 { + // return x; + // } else { + // ls = tl; + // i -= 1; + // } + // } + // _ => { + // unreachable!(); // <-- best candidate (Rustc introduces an `unreachable` case) + // } + // } + // } + // ``` + // + // Adding this sanity check so that we can see when there are + // several candidates. + assert!(candidates.is_empty()); + chosen_loop_exits.insert(loop_id, None); + } } else { // Register the exit, if there is one match best_exit { @@ -1305,14 +1403,14 @@ fn compute_loop_switch_exits(cfg_info: &CfgInfo) -> ExitInfo { } fn combine_statement_and_statement( - statement: tgt::Statement, - next_st: Option, -) -> tgt::Statement { + statement: Box, + next_st: Option>, +) -> Box { match next_st { Some(next_st) => { let meta = combine_meta(&statement.meta, &next_st.meta); - let st = tgt::RawStatement::Sequence(Box::new(statement), Box::new(next_st)); - tgt::Statement::new(meta, st) + let st = tgt::RawStatement::Sequence(statement, next_st); + Box::new(tgt::Statement::new(meta, st)) } None => statement, } @@ -1320,11 +1418,15 @@ fn combine_statement_and_statement( fn combine_statements_and_statement( statements: Vec, - next: Option, -) -> Option { - statements.into_iter().rev().fold(next, |seq, st| { - Some(combine_statement_and_statement(st, seq)) - }) + next: Option>, +) -> Option> { + statements + .into_iter() + .map(Box::new) + .rev() + .fold(next, |seq, st| { + Some(combine_statement_and_statement(st, seq)) + }) } fn get_goto_kind( @@ -1374,20 +1476,20 @@ enum GotoKind { /// We use the one for the parent terminator. fn translate_child_block( info: &mut BlockInfo<'_>, - parent_loops: Vector, + parent_loops: &Vector, switch_exit_blocks: &im::HashSet, parent_meta: Meta, child_id: src::BlockId::Id, -) -> Option { +) -> Option> { // Check if this is a backward call - match get_goto_kind(info.exits_info, &parent_loops, switch_exit_blocks, child_id) { + match get_goto_kind(info.exits_info, parent_loops, switch_exit_blocks, child_id) { GotoKind::Break(index) => { let st = tgt::RawStatement::Break(index); - Some(tgt::Statement::new(parent_meta, st)) + Some(Box::new(tgt::Statement::new(parent_meta, st))) } GotoKind::Continue(index) => { let st = tgt::RawStatement::Continue(index); - Some(tgt::Statement::new(parent_meta, st)) + Some(Box::new(tgt::Statement::new(parent_meta, st))) } // If we are going to an exit block we simply ignore the goto GotoKind::ExitBlock => None, @@ -1398,10 +1500,13 @@ fn translate_child_block( } } -fn opt_statement_to_nop_if_none(meta: Meta, opt_st: Option) -> tgt::Statement { +fn opt_statement_to_nop_if_none( + meta: Meta, + opt_st: Option>, +) -> Box { match opt_st { Some(st) => st, - None => tgt::Statement::new(meta, tgt::RawStatement::Nop), + None => Box::new(tgt::Statement::new(meta, tgt::RawStatement::Nop)), } } @@ -1430,19 +1535,20 @@ fn translate_statement(st: &src::Statement) -> Option { fn translate_terminator( info: &mut BlockInfo<'_>, - parent_loops: Vector, + parent_loops: &Vector, switch_exit_blocks: &im::HashSet, terminator: &src::Terminator, -) -> Option { +) -> Option> { let src_meta = terminator.meta; match &terminator.content { - src::RawTerminator::Panic | src::RawTerminator::Unreachable => { - Some(tgt::Statement::new(src_meta, tgt::RawStatement::Panic)) - } - src::RawTerminator::Return => { - Some(tgt::Statement::new(src_meta, tgt::RawStatement::Return)) - } + src::RawTerminator::Panic | src::RawTerminator::Unreachable => Some(Box::new( + tgt::Statement::new(src_meta, tgt::RawStatement::Panic), + )), + src::RawTerminator::Return => Some(Box::new(tgt::Statement::new( + src_meta, + tgt::RawStatement::Return, + ))), src::RawTerminator::Goto { target } => translate_child_block( info, parent_loops, @@ -1458,7 +1564,10 @@ fn translate_terminator( terminator.meta, *target, ); - let st = tgt::Statement::new(src_meta, tgt::RawStatement::Drop(place.clone())); + let st = Box::new(tgt::Statement::new( + src_meta, + tgt::RawStatement::Drop(place.clone()), + )); Some(combine_statement_and_statement(st, opt_child)) } src::RawTerminator::Call { call, target } => { @@ -1470,7 +1579,7 @@ fn translate_terminator( *target, ); let st = tgt::RawStatement::Call(call.clone()); - let st = tgt::Statement::new(src_meta, st); + let st = Box::new(tgt::Statement::new(src_meta, st)); Some(combine_statement_and_statement(st, opt_child)) } src::RawTerminator::Assert { @@ -1489,7 +1598,7 @@ fn translate_terminator( cond: cond.clone(), expected: *expected, }); - let st = tgt::Statement::new(src_meta, st); + let st = Box::new(tgt::Statement::new(src_meta, st)); Some(combine_statement_and_statement(st, opt_child)) } src::RawTerminator::Switch { discr, targets } => { @@ -1499,7 +1608,7 @@ fn translate_terminator( // Translate the children expressions let then_exp = translate_child_block( info, - parent_loops.clone(), + parent_loops, switch_exit_blocks, terminator.meta, *then_tgt, @@ -1517,7 +1626,7 @@ fn translate_terminator( let else_exp = opt_statement_to_nop_if_none(terminator.meta, else_exp); // Translate - tgt::Switch::If(discr.clone(), Box::new(then_exp), Box::new(else_exp)) + tgt::Switch::If(discr.clone(), then_exp, else_exp) } src::SwitchTargets::SwitchInt(int_ty, targets, otherwise) => { // Note that some branches can be grouped together, like @@ -1557,7 +1666,7 @@ fn translate_terminator( // Not translated: translate it let exp = translate_child_block( info, - parent_loops.clone(), + parent_loops, switch_exit_blocks, terminator.meta, *bid, @@ -1565,7 +1674,7 @@ fn translate_terminator( // We use the terminator meta information in case then // then statement is `None` let exp = opt_statement_to_nop_if_none(terminator.meta, exp); - branches.insert(*bid, (vec![*v], exp)); + branches.insert(*bid, (vec![*v], *exp)); } } let targets_exps: Vec<(Vec, tgt::Statement)> = @@ -1584,12 +1693,7 @@ fn translate_terminator( opt_statement_to_nop_if_none(terminator.meta, otherwise_exp); // Translate - tgt::Switch::SwitchInt( - discr.clone(), - *int_ty, - targets_exps, - Box::new(otherwise_exp), - ) + tgt::Switch::SwitchInt(discr.clone(), *int_ty, targets_exps, otherwise_exp) } }; @@ -1597,24 +1701,24 @@ fn translate_terminator( let meta = tgt::combine_switch_targets_meta(&switch); let meta = combine_meta(&src_meta, &meta); let st = tgt::RawStatement::Switch(switch); - let st = tgt::Statement::new(meta, st); + let st = Box::new(tgt::Statement::new(meta, st)); Some(st) } } } fn combine_expressions( - exp1: Option, - exp2: Option, -) -> Option { + exp1: Option>, + exp2: Option>, +) -> Option> { match exp1 { None => exp2, Some(exp1) => match exp2 { None => Some(exp1), Some(exp2) => { let meta = combine_meta(&exp1.meta, &exp2.meta); - let st = tgt::RawStatement::Sequence(Box::new(exp1), Box::new(exp2)); - Some(tgt::Statement::new(meta, st)) + let st = tgt::RawStatement::Sequence(exp1, exp2); + Some(Box::new(tgt::Statement::new(meta, st))) } }, } @@ -1656,13 +1760,16 @@ fn is_terminal_explore(num_loops: usize, st: &tgt::Statement) -> bool { } } +/// Remark: some values are boxed (here, the returned statement) so that they +/// are allocated on the heap. This reduces stack usage (we had problems with +/// stack overflows in the past). A more efficient solution would be to use loops +/// to make this code constant space, but that would require a serious rewriting. fn translate_block( info: &mut BlockInfo<'_>, - parent_loops: Vector, - // TODO: remove the shared borrow? + parent_loops: &Vector, switch_exit_blocks: &im::HashSet, block_id: src::BlockId::Id, -) -> Option { +) -> Option> { // If the user activated this check: check that we didn't already translate // this block, and insert the block id in the set of already translated blocks. trace!( @@ -1680,12 +1787,13 @@ fn translate_block( // Check if we enter a loop: if so, update parent_loops and the current_exit_block let is_loop = info.cfg.loop_entries.contains(&block_id); + let mut nparent_loops: Vector; let nparent_loops = if info.cfg.loop_entries.contains(&block_id) { - let mut nparent_loops = parent_loops.clone(); + nparent_loops = parent_loops.clone(); nparent_loops.push_back(block_id); - nparent_loops + &nparent_loops } else { - parent_loops.clone() + parent_loops }; // If we enter a switch or a loop, we need to check if we own the exit @@ -1741,7 +1849,7 @@ fn translate_block( // Put the whole loop body inside a `Loop` wrapper let exp = exp.unwrap(); - let exp = tgt::Statement::new(exp.meta, tgt::RawStatement::Loop(Box::new(exp))); + let exp = Box::new(tgt::Statement::new(exp.meta, tgt::RawStatement::Loop(exp))); // Add the exit block if let Some(exit_block_id) = next_block { @@ -1801,7 +1909,7 @@ fn translate_body(no_code_duplication: bool, src_body: &src::ExprBody) -> tgt::E }; let stmt = translate_block( &mut info, - Vector::new(), + &Vector::new(), &im::HashSet::new(), src::BlockId::ZERO, ) @@ -1816,63 +1924,54 @@ fn translate_body(no_code_duplication: bool, src_body: &src::ExprBody) -> tgt::E meta: src_body.meta, arg_count: src_body.arg_count, locals: src_body.locals.clone(), - body: stmt, + body: *stmt, } } -/// `type_defs`, `global_defs`: those parameters are used for pretty-printing purposes -fn translate_function( - no_code_duplication: bool, - type_defs: &TypeDecls, - src_defs: &src::FunDecls, - src_def_id: FunDeclId::Id, - global_defs: &src::GlobalDecls, -) -> tgt::FunDecl { +/// TODO: put `no_code +fn translate_function(ctx: &TransCtx, src_def_id: FunDeclId::Id) -> tgt::FunDecl { // Retrieve the function definition - let src_def = src_defs.get(src_def_id).unwrap(); + let src_def = ctx.fun_defs.get(src_def_id).unwrap(); trace!( - "# Reconstructing: {}\n\n{}", - src_def.name, - src_def.fmt_with_decls(type_defs, src_defs, global_defs) + "# About to reconstruct: {}\n\n{}", + src_def.name.fmt_with_ctx(ctx), + ctx.format_object(src_def) ); // Return the translated definition tgt::FunDecl { def_id: src_def.def_id, meta: src_def.meta, + is_local: src_def.is_local, name: src_def.name.clone(), signature: src_def.signature.clone(), + kind: src_def.kind.clone(), body: src_def .body .as_ref() - .map(|b| translate_body(no_code_duplication, b)), + .map(|b| translate_body(ctx.no_code_duplication, b)), } } -fn translate_global( - no_code_duplication: bool, - type_defs: &TypeDecls, - global_defs: &src::GlobalDecls, - global_id: GlobalDeclId::Id, - fun_defs: &src::FunDecls, -) -> tgt::GlobalDecl { +fn translate_global(ctx: &TransCtx, global_id: GlobalDeclId::Id) -> tgt::GlobalDecl { // Retrieve the global definition - let src_def = global_defs.get(global_id).unwrap(); + let src_def = ctx.global_defs.get(global_id).unwrap(); trace!( - "# Reconstructing: {}\n\n{}", - src_def.name, - src_def.fmt_with_decls(type_defs, fun_defs, global_defs) + "# About to reconstruct: {}\n\n{}", + src_def.name.fmt_with_ctx(ctx), + ctx.format_object(src_def) ); tgt::GlobalDecl { def_id: src_def.def_id, meta: src_def.meta, + is_local: src_def.is_local, name: src_def.name.clone(), ty: src_def.ty.clone(), body: src_def .body .as_ref() - .map(|b| translate_body(no_code_duplication, b)), + .map(|b| translate_body(ctx.no_code_duplication, b)), } } @@ -1882,55 +1981,32 @@ fn translate_global( /// can be a sign that the reconstruction is of poor quality, but sometimes /// code duplication is necessary, in the presence of "fused" match branches for /// instance). -pub fn translate_functions( - no_code_duplication: bool, - type_defs: &TypeDecls, - src_funs: &src::FunDecls, - src_globals: &src::GlobalDecls, -) -> Defs { +pub fn translate_functions(ctx: &TransCtx) -> Defs { let mut tgt_funs = FunDeclId::Map::new(); let mut tgt_globals = GlobalDeclId::Map::new(); // Translate the bodies one at a time - for (fun_id, _) in src_funs.iter_indexed() { - tgt_funs.insert( - *fun_id, - translate_function( - no_code_duplication, - type_defs, - src_funs, - *fun_id, - src_globals, - ), - ); + for (fun_id, _) in ctx.fun_defs.iter_indexed() { + tgt_funs.insert(*fun_id, translate_function(ctx, *fun_id)); } - for (global_id, _) in src_globals.iter_indexed() { - tgt_globals.insert( - *global_id, - translate_global( - no_code_duplication, - type_defs, - src_globals, - *global_id, - src_funs, - ), - ); + for (global_id, _) in ctx.global_defs.iter_indexed() { + tgt_globals.insert(*global_id, translate_global(ctx, *global_id)); } // Print the functions for (_, fun) in &tgt_funs { trace!( "# Signature:\n{}\n\n# Function definition:\n{}\n", - fun.signature.fmt_with_decls(type_defs, src_globals), - fun.fmt_with_decls(type_defs, &tgt_funs, &tgt_globals) + ctx.format_object(&fun.signature), + ctx.format_object(fun), ); } // Print the global variables for (_, global) in &tgt_globals { trace!( - "# Type:\n{:?}\n\n# Global definition:\n{}\n", - global.ty, - global.fmt_with_decls(type_defs, &tgt_funs, &tgt_globals) + "# Type:\n{}\n\n# Global definition:\n{}\n", + ctx.format_object(&global.ty), + ctx.format_object(global) ); } diff --git a/charon/src/values.rs b/charon/src/values.rs index ad95b214..0229db3e 100644 --- a/charon/src/values.rs +++ b/charon/src/values.rs @@ -1,8 +1,6 @@ //! Contains definitions for variables and constant values. -#![allow(dead_code)] - -pub use crate::values_utils::DummyFormatter; // Don't understand why we need to to this +pub use crate::values_utils::DummyFormatter; // Don't understand why we need to do this pub use crate::values_utils::*; use core::hash::Hash; use macros::{generate_index_type, EnumAsGetters, EnumIsA, VariantIndexArity, VariantName}; @@ -19,7 +17,19 @@ generate_index_type!(VarId); /// A primitive value. /// /// Those are for instance used for the constant operands [crate::expressions::Operand::Const] -#[derive(Debug, PartialEq, Eq, Clone, VariantName, EnumIsA, EnumAsGetters, Serialize)] +#[derive( + Debug, + PartialEq, + Eq, + Clone, + VariantName, + EnumIsA, + EnumAsGetters, + Serialize, + Hash, + PartialOrd, + Ord, +)] pub enum Literal { Scalar(ScalarValue), Bool(bool), @@ -35,7 +45,18 @@ pub enum Literal { /// the values to integers, leading to potential overflows: we implement a custom /// serialization, which serializes the values to strings. #[derive( - Debug, PartialEq, Eq, Copy, Clone, EnumIsA, EnumAsGetters, VariantName, VariantIndexArity, Hash, + Debug, + PartialEq, + Eq, + Copy, + Clone, + EnumIsA, + EnumAsGetters, + VariantName, + VariantIndexArity, + Hash, + PartialOrd, + Ord, )] pub enum ScalarValue { /// Using i64 to be safe diff --git a/charon/src/values_utils.rs b/charon/src/values_utils.rs index 822ca2f9..1298f886 100644 --- a/charon/src/values_utils.rs +++ b/charon/src/values_utils.rs @@ -1,15 +1,12 @@ //! Implementations for [crate::values] - -#![allow(dead_code)] - use crate::formatter::Formatter; use crate::types::*; -use crate::ullbc_ast::GlobalDeclId; +use crate::ullbc_ast::{FunDeclId, GlobalDeclId}; use crate::values::*; use serde::{Serialize, Serializer}; impl VarId::Id { - pub fn to_pretty_string(&self) -> String { + pub fn to_pretty_string(self) -> String { format!("@{self}") } } @@ -24,6 +21,7 @@ pub enum ScalarError { /// Our redefinition of Result - we don't care much about the I/O part. pub type ScalarResult = std::result::Result; +/// Dummy formatter, which doesn't perform any lookup when formatting an identifier. pub struct DummyFormatter {} impl Formatter for DummyFormatter { @@ -70,15 +68,33 @@ impl Formatter<(TypeDeclId::Id, Option, FieldId::Id)> for DummyFo } } -impl Formatter<&ErasedRegion> for DummyFormatter { - fn format_object(&self, _: &ErasedRegion) -> String { - "'_".to_string() +impl Formatter for DummyFormatter { + fn format_object(&self, id: TraitClauseId::Id) -> String { + id.to_pretty_string() + } +} + +impl Formatter for DummyFormatter { + fn format_object(&self, id: FunDeclId::Id) -> String { + id.to_pretty_string() + } +} + +impl Formatter for DummyFormatter { + fn format_object(&self, id: TraitDeclId::Id) -> String { + id.to_pretty_string() + } +} + +impl Formatter for DummyFormatter { + fn format_object(&self, id: TraitImplId::Id) -> String { + id.to_pretty_string() } } -impl Formatter<&Region> for DummyFormatter { - fn format_object(&self, r: &Region) -> String { - r.to_string() +impl Formatter for DummyFormatter { + fn format_object(&self, id: RegionId::Id) -> String { + id.to_pretty_string() } } diff --git a/flake.lock b/flake.lock index 0b9d5e3e..d4eafdf3 100644 --- a/flake.lock +++ b/flake.lock @@ -2,21 +2,16 @@ "nodes": { "crane": { "inputs": { - "flake-compat": "flake-compat", - "flake-utils": [ - "flake-utils" - ], "nixpkgs": [ "nixpkgs" - ], - "rust-overlay": "rust-overlay" + ] }, "locked": { - "lastModified": 1691423162, - "narHash": "sha256-cReUZCo83YEEmFcHX8CcOVTZYUrcWgHQO34zxQzy7WI=", + "lastModified": 1701220101, + "narHash": "sha256-EBuCZ/Vjp3ovx8ZvfALfuUk4/76Ey/6cJmzmeXBRmDk=", "owner": "ipetkov", "repo": "crane", - "rev": "b5d9d42ea3fa8fea1805d9af1416fe207d0dd1dc", + "rev": "514cd663e5af505a244e55ad013733638574aff9", "type": "github" }, "original": { @@ -25,32 +20,16 @@ "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -61,11 +40,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1691472822, - "narHash": "sha256-XVfYZ2oB3lNPVq6sHCY9WkdQ8lHoIDzzbpg8bB6oBxA=", + "lastModified": 1701068326, + "narHash": "sha256-vmMceA+q6hG1yrjb+MP8T0YFDQIrW3bl45e7z24IEts=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "41c7605718399dcfa53dd7083793b6ae3bc969ff", + "rev": "8cfef6986adfb599ba379ae53c9f5631ecd2fd9c", "type": "github" }, "original": { @@ -79,35 +58,10 @@ "crane": "crane", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay_2" + "rust-overlay": "rust-overlay" } }, "rust-overlay": { - "inputs": { - "flake-utils": [ - "crane", - "flake-utils" - ], - "nixpkgs": [ - "crane", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1691029059, - "narHash": "sha256-QwVeE9YTgH3LmL7yw2V/hgswL6yorIvYSp4YGI8lZYM=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "99df4908445be37ddb2d332580365fce512a7dcf", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "rust-overlay_2": { "inputs": { "flake-utils": [ "flake-utils" @@ -117,11 +71,11 @@ ] }, "locked": { - "lastModified": 1691547503, - "narHash": "sha256-l0AIKJucygbDFc2vuAkxmFMjNNJImDd7jYahA88/E+o=", + "lastModified": 1701224160, + "narHash": "sha256-qnMmxNMKmd6Soel0cfauyMJ+LzuZbvmiDQPSIuTbQ+M=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "3380f16b39457b49c8186d5e20e7a68ccf4fc96e", + "rev": "4a080e26d55eaedb95ab1bf8eeaeb84149c10f12", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 27c53ffa..2618eabe 100644 --- a/flake.nix +++ b/flake.nix @@ -48,7 +48,7 @@ (crane.mkLib pkgs).overrideToolchain rustToolchainWithExt; craneLibNoExt = (crane.mkLib pkgs).overrideToolchain rustToolchainNoExt; charon = - let cargoArtifacts = craneLibNoExt.buildDepsOnly { src = ./charon; }; + let cargoArtifacts = craneLibWithExt.buildDepsOnly { src = ./charon; }; in craneLibWithExt.buildPackage { src = ./charon; inherit cargoArtifacts; @@ -101,6 +101,22 @@ }; buildInputs = [ ocamlPackages.calendar ]; }; + charon-name_matcher_parser = + ocamlPackages.buildDunePackage { + pname = "name_matcher_parser"; + version = "0.1.0"; + duneVersion = "3"; + nativeBuildInputs = with ocamlPackages; [ + menhir + ]; + propagatedBuildInputs = with ocamlPackages; [ + ppx_deriving + visitors + zarith + menhirLib + ]; + src = ./charon-ml; + }; mk-charon-ml = doCheck: ocamlPackages.buildDunePackage { pname = "charon"; @@ -114,13 +130,14 @@ cp ${tests-polonius}/llbc/* tests/serialized '' else ""; - buildInputs = with ocamlPackages; [ + propagatedBuildInputs = with ocamlPackages; [ ppx_deriving visitors easy_logging zarith yojson calendar + charon-name_matcher_parser ]; src = ./charon-ml; inherit doCheck; @@ -133,8 +150,5 @@ default = charon; }; checks = { inherit tests tests-polonius charon-ml-tests; }; - hydraJobs = { - inherit charon tests tests-polonius charon-ml charon-ml-tests; - }; }); } diff --git a/tests-polonius/Makefile b/tests-polonius/Makefile index 0168b6c2..265e74b0 100644 --- a/tests-polonius/Makefile +++ b/tests-polonius/Makefile @@ -1,7 +1,7 @@ CURRENT_DIR = $(shell pwd) CHARON ?= $(CURRENT_DIR)/../bin/charon DEST ?= . -OPTIONS = --polonius +OPTIONS += --polonius NOT_ALL_TESTS ?= .PHONY: all diff --git a/tests/Makefile b/tests/Makefile index 7b9df358..ec262458 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,7 +1,7 @@ CURRENT_DIR = $(shell pwd) CHARON ?= $(CURRENT_DIR)/../bin/charon DEST ?= . -OPTIONS = +OPTIONS ?= CHARON_CMD := NOT_ALL_TESTS ?= @@ -25,12 +25,13 @@ charon-tests: \ test-loops test-loops_cfg test-hashmap \ test-paper test-hashmap_main \ test-matches test-matches_duplicate test-external \ - test-constants test-array test-array2 test-shifts + test-constants test-array test-array_const_generics test-traits \ + test-closures test-shifts test-nested_borrows: OPTIONS += --no-code-duplication test-no_nested_borrows: OPTIONS += --no-code-duplication test-loops: -test-loops_cfg: OPTIONS += --no-code-duplication +test-loops_cfg: test-hashmap: test-hashmap_main: OPTIONS += --opaque=hashmap_utils test-paper: OPTIONS += --no-code-duplication @@ -40,7 +41,11 @@ test-matches: test-external: OPTIONS += --no-code-duplication test-matches_duplicate: test-array: -test-array2: +test-array_const_generics: +test-traits: +test-traits_special: +test-closures: +test-shifts: # ============================================================================= # The tests. diff --git a/tests/src/array.rs b/tests/src/array.rs index 776c7b3a..bde2c0dd 100644 --- a/tests/src/array.rs +++ b/tests/src/array.rs @@ -1,122 +1,144 @@ //! Exercise the translation of arrays, with features supported by Eurydice -#![allow(dead_code)] + +pub enum AB { + A, + B, +} + +pub fn incr(x: &mut u32) { + *x += 1; +} // Nano-tests // ---------- // The suffix `_` prevents name collisions in some backends -fn array_to_shared_slice_(s: &[T; 32]) -> &[T] { +pub fn array_to_shared_slice_(s: &[T; 32]) -> &[T] { s } // The suffix `_` prevents name collisions in some backends -fn array_to_mut_slice_(s: &mut [T; 32]) -> &mut [T] { +pub fn array_to_mut_slice_(s: &mut [T; 32]) -> &mut [T] { s } -fn array_len(s: [T; 32]) -> usize { +pub fn array_len(s: [T; 32]) -> usize { s.len() } -fn shared_array_len(s: &[T; 32]) -> usize { +pub fn shared_array_len(s: &[T; 32]) -> usize { s.len() } -fn shared_slice_len(s: &[T]) -> usize { +pub fn shared_slice_len(s: &[T]) -> usize { s.len() } -fn index_array_shared(s: &[T; 32], i: usize) -> &T { +pub fn index_array_shared(s: &[T; 32], i: usize) -> &T { &s[i] } // Remark: can't move out of an array // Also: can't move out of a slice. -fn index_array_u32(s: [u32; 32], i: usize) -> u32 { +pub fn index_array_u32(s: [u32; 32], i: usize) -> u32 { s[i] } -fn index_array_copy(x: &[u32; 32]) -> u32 { +pub fn index_array_copy(x: &[u32; 32]) -> u32 { x[0] } -fn index_mut_array(s: &mut [T; 32], i: usize) -> &mut T { +pub fn index_mut_array(s: &mut [T; 32], i: usize) -> &mut T { &mut s[i] } -fn index_slice(s: &[T], i: usize) -> &T { +pub fn index_slice(s: &[T], i: usize) -> &T { &s[i] } -fn index_mut_slice(s: &mut [T], i: usize) -> &mut T { +pub fn index_mut_slice(s: &mut [T], i: usize) -> &mut T { &mut s[i] } -fn slice_subslice_shared_(x: &[u32], y: usize, z: usize) -> &[u32] { +pub fn slice_subslice_shared_(x: &[u32], y: usize, z: usize) -> &[u32] { &x[y..z] } -fn slice_subslice_mut_(x: &mut [u32], y: usize, z: usize) -> &mut [u32] { +pub fn slice_subslice_mut_(x: &mut [u32], y: usize, z: usize) -> &mut [u32] { &mut x[y..z] } -fn array_to_slice_shared_(x: &[u32; 32]) -> &[u32] { +pub fn array_to_slice_shared_(x: &[u32; 32]) -> &[u32] { x } -fn array_to_slice_mut_(x: &mut [u32; 32]) -> &mut [u32] { +pub fn array_to_slice_mut_(x: &mut [u32; 32]) -> &mut [u32] { x } -fn array_subslice_shared_(x: &[u32; 32], y: usize, z: usize) -> &[u32] { +pub fn array_subslice_shared_(x: &[u32; 32], y: usize, z: usize) -> &[u32] { &x[y..z] } -fn array_subslice_mut_(x: &mut [u32; 32], y: usize, z: usize) -> &mut [u32] { +pub fn array_subslice_mut_(x: &mut [u32; 32], y: usize, z: usize) -> &mut [u32] { &mut x[y..z] } -fn index_slice_0(s: &[T]) -> &T { +pub fn index_slice_0(s: &[T]) -> &T { &s[0] } -fn index_array_0(s: &[T; 32]) -> &T { +pub fn index_array_0(s: &[T; 32]) -> &T { &s[0] } /* // Unsupported by Aeneas for now -fn index_index_slice<'a, T>(s: &'a [&[T]], i: usize, j: usize) -> &'a T { +pub fn index_index_slice<'a, T>(s: &'a [&[T]], i: usize, j: usize) -> &'a T { &s[i][j] } */ -fn index_index_array(s: [[u32; 32]; 32], i: usize, j: usize) -> u32 { +pub fn index_index_array(s: [[u32; 32]; 32], i: usize, j: usize) -> u32 { s[i][j] } /* // Unsupported by Aeneas for now -fn update_update_slice(s: &mut [&mut [u32]], i: usize, j: usize) { +pub fn update_update_slice(s: &mut [&mut [u32]], i: usize, j: usize) { s[i][j] = 0; } */ -fn update_update_array(mut s: [[u32; 32]; 32], i: usize, j: usize) { +pub fn update_update_array(mut s: [[u32; 32]; 32], i: usize, j: usize) { s[i][j] = 0; } -fn array_local_deep_copy(x: &[u32; 32]) { +pub fn array_local_deep_copy(x: &[u32; 32]) { let _y = *x; } -fn take_array(_: [u32; 2]) {} -fn take_array_borrow(_: &[u32; 2]) {} -fn take_slice(_: &[u32]) {} -fn take_mut_slice(_: &mut [u32]) {} +pub fn take_array(_: [u32; 2]) {} +pub fn take_array_borrow(_: &[u32; 2]) {} +pub fn take_slice(_: &[u32]) {} +pub fn take_mut_slice(_: &mut [u32]) {} + +pub fn const_array() -> [u32; 2] { + [0, 0] +} + +pub fn const_slice() { + let _: &[u32] = &[0, 0]; +} + +/* +// This triggers a special case in the constant expressions +pub fn const_string() { + let _ = "hello"; +}*/ -fn take_all() { +pub fn take_all() { let mut x: [u32; 2] = [0, 0]; // x is deep copied (copy node appears in Charon, followed by a move) take_array(x); @@ -130,27 +152,27 @@ fn take_all() { take_mut_slice(&mut x); } -fn index_array(x: [u32; 2]) -> u32 { +pub fn index_array(x: [u32; 2]) -> u32 { x[0] } -fn index_array_borrow(x: &[u32; 2]) -> u32 { +pub fn index_array_borrow(x: &[u32; 2]) -> u32 { x[0] } -fn index_slice_u32_0(x: &[u32]) -> u32 { +pub fn index_slice_u32_0(x: &[u32]) -> u32 { x[0] } -fn index_mut_slice_u32_0(x: &mut [u32]) -> u32 { +pub fn index_mut_slice_u32_0(x: &mut [u32]) -> u32 { x[0] } -fn index_all() -> u32 { +pub fn index_all() -> u32 { let mut x: [u32; 2] = [0, 0]; if true { - let mut y: [u32; 2] = [0, 0]; + let _y: [u32; 2] = [0, 0]; } else { - let mut z: [u32; 2] = [0, 0]; + let _z: [u32; 1] = [0]; } index_array(x) + index_array(x) @@ -159,17 +181,17 @@ fn index_all() -> u32 { + index_mut_slice_u32_0(&mut x) } -fn update_array(mut x: [u32; 2]) { +pub fn update_array(mut x: [u32; 2]) { x[0] = 1 } -fn update_array_mut_borrow(x: &mut [u32; 2]) { +pub fn update_array_mut_borrow(x: &mut [u32; 2]) { x[0] = 1 } -fn update_mut_slice(x: &mut [u32]) { +pub fn update_mut_slice(x: &mut [u32]) { x[0] = 1 } -fn update_all() { +pub fn update_all() { let mut x: [u32; 2] = [0, 0]; update_array(x); update_array(x); @@ -180,7 +202,7 @@ fn update_all() { // Nano-tests, with ranges // ----------------------- -fn range_all() { +pub fn range_all() { let mut x: [u32; 4] = [0, 0, 0, 0]; // CONFIRM: there is no way to shrink [T;N] into [T;M] with M u32 { +pub fn deref_array_borrow(x: &[u32; 2]) -> u32 { let x: [u32; 2] = *x; x[0] } -fn deref_array_mut_borrow(x: &mut [u32; 2]) -> u32 { +pub fn deref_array_mut_borrow(x: &mut [u32; 2]) -> u32 { let x: [u32; 2] = *x; x[0] } @@ -202,15 +224,10 @@ fn deref_array_mut_borrow(x: &mut [u32; 2]) -> u32 { // Non-copiable arrays // ------------------- -enum T { - A, - B, -} - -fn take_array_t(_: [T; 2]) {} +pub fn take_array_t(_: [AB; 2]) {} -fn non_copyable_array() { - let x: [T; 2] = [T::A, T::B]; +pub fn non_copyable_array() { + let x: [AB; 2] = [AB::A, AB::B]; // x is moved (not deep copied!) // TODO: determine whether the translation needs to be aware of that and pass by ref instead of by copy take_array_t(x); @@ -222,7 +239,7 @@ fn non_copyable_array() { // Larger, random tests // -------------------- -fn sum(s: &[u32]) -> u32 { +pub fn sum(s: &[u32]) -> u32 { let mut sum = 0; let mut i = 0; while i < s.len() { @@ -232,7 +249,7 @@ fn sum(s: &[u32]) -> u32 { sum } -fn sum2(s: &[u32], s2: &[u32]) -> u32 { +pub fn sum2(s: &[u32], s2: &[u32]) -> u32 { let mut sum = 0; assert!(s.len() == s2.len()); let mut i = 0; @@ -243,34 +260,38 @@ fn sum2(s: &[u32], s2: &[u32]) -> u32 { sum } -fn f0() { +pub fn f0() { let s: &mut [u32] = &mut [1, 2]; s[0] = 1; } -fn f1() { +pub fn f1() { let mut s: [u32; 2] = [1, 2]; s[0] = 1; } -fn f2(_: u32) {} +pub fn f2(_: u32) {} -fn f3() -> u32 { +pub fn f3() -> u32 { let a: [u32; 2] = [1, 2]; f2(a[0]); let b = [0; 32]; sum2(&a, f4(&b, 16, 18)) } -// TODO: this makes the compilation fail -//const SZ: usize = 32; - -fn f4(x: &[u32; 32], y: usize, z: usize) -> &[u32] { +pub fn f4(x: &[u32; 32], y: usize, z: usize) -> &[u32] { &x[y..z] } +pub const SZ: usize = 32; + +// There is something slightly annoying here: the SZ constant gets inlined +pub fn f5(x: &[u32; SZ]) -> u32 { + x[0] +} + // To avoid lifetime shortening -fn ite() { +pub fn ite() { let mut x: [u32; 2] = [0, 0]; if true { let mut y: [u32; 2] = [0, 0]; diff --git a/tests/src/array2.rs b/tests/src/array2.rs deleted file mode 100644 index 5118711a..00000000 --- a/tests/src/array2.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Exercise the translation of arrays, features not yet supported by Eurydice -#![allow(dead_code)] - -fn index_array_generic(s: [u32; N], i: usize) -> u32 { - s[i] -} - -fn index_array_generic_call(s: [u32; N], i: usize) -> u32 { - index_array_generic(s, i) -} - diff --git a/tests/src/array_const_generics.rs b/tests/src/array_const_generics.rs new file mode 100644 index 00000000..c7eea054 --- /dev/null +++ b/tests/src/array_const_generics.rs @@ -0,0 +1,20 @@ +//! Exercise the translation of arrays, features not yet supported by Eurydice + +pub fn index_array_generic(s: [u32; N], i: usize) -> u32 { + s[i] +} + +pub fn index_array_generic_call(s: [u32; N], i: usize) -> u32 { + index_array_generic(s, i) +} + +// Using const generics as values +pub fn const_gen_ret() -> usize { + N +} + +// Comes from https://github.com/AeneasVerif/charon/issues/45 +// We initialize an array with a variable length (this uses the `repeat` instruction). +pub fn init_array_variable_len() -> [u8; LEN] { + [0u8; LEN] +} diff --git a/tests/src/closures.rs b/tests/src/closures.rs new file mode 100644 index 00000000..8c80c9ce --- /dev/null +++ b/tests/src/closures.rs @@ -0,0 +1,109 @@ +/*pub fn incr_u32(x: u32) -> u32 { + x + 1 +} + +/* +/* Testing function pointers and closures */ +// TODO: this requires to take into account the constraints over associated types +// because the output type of the Fn trait is an associated type, not a parameter. +// More precisely, we have the constraint that: +// >::Output = T +#[allow(clippy::manual_map)] +pub fn map_option(x: Option, f: F) -> Option +where + F: Fn(T) -> T, +{ + match x { + None => None, + Some(x) => Some(f(x)), + } +} +*/ + +// With a pointer to a top level function +pub fn test_map_option1(x: Option) -> Option { + map_option(x, incr_u32) +} +*/ + +/* +// +pub fn test_closure1(x: &T) -> &T { + //let f: fn(&T) -> &T = |x| x; + let f: fn(u32) -> u32 = |x| x; + //(f)(x) + x +} +*/ + +/* +// With a local function +pub fn test_map_option2(x: Option) -> Option { + let f: fn(u32) -> u32 = |x| x + 1; + map_option(x, f) +} + +/* +// With a local function which uses local type variables +pub fn test_map_option3(_: T, x: Option) -> Option { + let f: fn(U) -> U = |x| x; + map_option(x, f) +} +*/ + +/*// With a `dyn` +pub fn test_map_option3(x: Option) -> Option { + let f: fn(u32) -> u32 = |x| x + 1; + map_option(x, f) +}*/ + +/* + +pub fn map(x: [i32; 256]) -> [i32; 256] { + x.map(|v| v) +} + +*/ +*/ + +/* +macro_rules! impl_generic_struct { + ($name:ident) => { + pub struct $name { + pub value: [u8; SIZE], + } + + impl TryFrom<&[u8]> for $name { + type Error = core::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + match value.try_into() { + Ok(value) => Ok(Self { value }), + Err(e) => Err(e), + } + } + } + }; +} + +impl_generic_struct!(KyberCiphertext); +*/ + +/* +use std::convert::{TryFrom, TryInto}; + +pub struct KyberCypherText { + pub value: [u8; SIZE], +} + +impl TryFrom<&[u8]> for KyberCypherText { + type Error = core::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + match value.try_into() { + Ok(value) => Ok(Self { value }), + Err(e) => Err(e), + } + } +} +*/ diff --git a/tests/src/constants.rs b/tests/src/constants.rs index 9cb6f8c0..67f1b4aa 100644 --- a/tests/src/constants.rs +++ b/tests/src/constants.rs @@ -1,83 +1,83 @@ //! Tests with constants -#![allow(dead_code)] // Integers -const X0: u32 = 0; +pub const X0: u32 = 0; -const X1: u32 = u32::MAX; +pub const X1: u32 = u32::MAX; -const X2: u32 = { +#[allow(clippy::let_and_return)] +pub const X2: u32 = { let x = 3; x }; -const X3: u32 = incr(32); +pub const X3: u32 = incr(32); -const fn incr(n: u32) -> u32 { +pub const fn incr(n: u32) -> u32 { n + 1 } // Pairs -const fn mk_pair0(x: u32, y: u32) -> (u32, u32) { +pub const fn mk_pair0(x: u32, y: u32) -> (u32, u32) { (x, y) } -const fn mk_pair1(x: u32, y: u32) -> Pair { +pub const fn mk_pair1(x: u32, y: u32) -> Pair { Pair { x, y } } -const P0: (u32, u32) = mk_pair0(0, 1); -const P1: Pair = mk_pair1(0, 1); -const P2: (u32, u32) = (0, 1); -const P3: Pair = Pair { x: 0, y: 1 }; +pub const P0: (u32, u32) = mk_pair0(0, 1); +pub const P1: Pair = mk_pair1(0, 1); +pub const P2: (u32, u32) = (0, 1); +pub const P3: Pair = Pair { x: 0, y: 1 }; pub struct Pair { - x: T1, - y: T2, + pub x: T1, + pub y: T2, } -const Y: Wrap = Wrap::new(2); +pub const Y: Wrap = Wrap::new(2); -const fn unwrap_y() -> i32 { - Y.val +pub const fn unwrap_y() -> i32 { + Y.value } -const YVAL: i32 = unwrap_y(); +pub const YVAL: i32 = unwrap_y(); -struct Wrap { - val: T, +pub struct Wrap { + value: T, } impl Wrap { - const fn new(val: T) -> Wrap { - Wrap { val } + pub const fn new(value: T) -> Wrap { + Wrap { value } } } // Additions -const fn get_z1() -> i32 { +pub const fn get_z1() -> i32 { const Z1: i32 = 3; Z1 } -const fn add(a: i32, b: i32) -> i32 { +pub const fn add(a: i32, b: i32) -> i32 { a + b } -const fn get_z2() -> i32 { +pub const fn get_z2() -> i32 { add(Q1, add(get_z1(), Q3)) } -const Q1: i32 = 5; -const Q2: i32 = Q1; -const Q3: i32 = add(Q2, 3); +pub const Q1: i32 = 5; +pub const Q2: i32 = Q1; +pub const Q3: i32 = add(Q2, 3); -// Statiques +// Static -static S1: u32 = 6; -static S2: u32 = incr(S1); -static S3: Pair = P3; -static S4: Pair = mk_pair1(7, 8); +pub static S1: u32 = 6; +pub static S2: u32 = incr(S1); +pub static S3: Pair = P3; +pub static S4: Pair = mk_pair1(7, 8); diff --git a/tests/src/external.rs b/tests/src/external.rs index 108b7d89..c8c10d0d 100644 --- a/tests/src/external.rs +++ b/tests/src/external.rs @@ -1,38 +1,38 @@ //! This module uses external types and functions -#![allow(dead_code)] use std::vec::Vec; /// This function uses an external function -fn swap<'a, T>(x: &'a mut T, y: &'a mut T) { +pub fn swap<'a, T>(x: &'a mut T, y: &'a mut T) { std::mem::swap(x, y) } /// This function uses external types and functions -fn test_new_non_zero_u32(x: u32) -> std::num::NonZeroU32 { +pub fn test_new_non_zero_u32(x: u32) -> std::num::NonZeroU32 { std::num::NonZeroU32::new(x).unwrap() } /// TODO: make vec external (rather than primitive) -fn test_vec() { +#[allow(clippy::vec_init_then_push)] +pub fn test_vec() { let mut v: Vec = Vec::new(); v.push(0); } /// Playing with a function in a state-error monad and which needs /// forward and backward translations. -fn custom_swap<'a, T>(x: &'a mut T, y: &'a mut T) -> &'a mut T { +pub fn custom_swap<'a, T>(x: &'a mut T, y: &'a mut T) -> &'a mut T { std::mem::swap(x, y); x } -fn test_custom_swap<'a>(x: &'a mut u32, y: &'a mut u32) { +pub fn test_custom_swap<'a>(x: &'a mut u32, y: &'a mut u32) { let z = custom_swap(x, y); *z = 1; } /// We just want a stateful example with a panic -fn test_swap_non_zero(mut x: u32) -> u32 { +pub fn test_swap_non_zero(mut x: u32) -> u32 { let mut y = 0; swap(&mut x, &mut y); diff --git a/tests/src/hashmap.rs b/tests/src/hashmap.rs index 8c691a9e..58d22acd 100644 --- a/tests/src/hashmap.rs +++ b/tests/src/hashmap.rs @@ -11,7 +11,6 @@ //! signatures, like in `&'a mut &'b mut T` (and the real problem comes from //! nested *lifetimes*, not nested borrows). Getting the borrows inside of //! enumerations mostly requires to pour some implementation time in it. -#![allow(dead_code)] use std::vec::Vec; pub type Key = usize; // TODO: make this generic @@ -312,6 +311,7 @@ impl HashMap { /// so I have to define the test functions somewhere and call them from /// a test function. /// TODO: find a way to do that. +#[allow(dead_code)] fn test1() { let mut hm: HashMap = HashMap::new(); hm.insert(0, 42); diff --git a/tests/src/hashmap_main.rs b/tests/src/hashmap_main.rs index 07c2b012..45dfa6e2 100644 --- a/tests/src/hashmap_main.rs +++ b/tests/src/hashmap_main.rs @@ -4,8 +4,7 @@ mod hashmap_utils; use crate::hashmap::*; use crate::hashmap_utils::*; -#[allow(dead_code)] -fn insert_on_disk(key: Key, value: u64) { +pub fn insert_on_disk(key: Key, value: u64) { // Deserialize let mut hm = deserialize(); // Update @@ -14,5 +13,4 @@ fn insert_on_disk(key: Key, value: u64) { serialize(hm); } -#[allow(dead_code)] -fn main() {} +pub fn main() {} diff --git a/tests/src/hashmap_utils.rs b/tests/src/hashmap_utils.rs index 5d9b27c6..cd7b481f 100644 --- a/tests/src/hashmap_utils.rs +++ b/tests/src/hashmap_utils.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use crate::hashmap::*; /// Serialize a hash map - we don't have traits, so we fix the type of the diff --git a/tests/src/lib.rs b/tests/src/lib.rs index f5154214..99211c96 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,11 +1,14 @@ -mod array; -mod array2; -mod constants; -mod external; -mod hashmap; -mod loops; -mod loops_cfg; -mod matches; -mod nested_borrows; -mod no_nested_borrows; -mod paper; +pub mod array; +pub mod array_const_generics; +pub mod closures; +pub mod constants; +pub mod external; +pub mod hashmap; +pub mod loops; +pub mod loops_cfg; +pub mod matches; +pub mod nested_borrows; +pub mod no_nested_borrows; +pub mod paper; +pub mod traits; +pub mod traits_special; diff --git a/tests/src/loops.rs b/tests/src/loops.rs index 1f3f8be4..45c4b918 100644 --- a/tests/src/loops.rs +++ b/tests/src/loops.rs @@ -1,9 +1,7 @@ -#![allow(dead_code)] - use std::vec::Vec; /// No borrows -fn sum(max: u32) -> u32 { +pub fn sum(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -18,7 +16,7 @@ fn sum(max: u32) -> u32 { /// Same as [sum], but we use borrows in order tocreate loans inside a loop /// iteration, and those borrows will have to be ended by the end of the /// iteration. -fn sum_with_mut_borrows(max: u32) -> u32 { +pub fn sum_with_mut_borrows(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -33,7 +31,7 @@ fn sum_with_mut_borrows(max: u32) -> u32 { } /// Similar to [sum_with_mut_borrows]. -fn sum_with_shared_borrows(max: u32) -> u32 { +pub fn sum_with_shared_borrows(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -51,7 +49,7 @@ fn sum_with_shared_borrows(max: u32) -> u32 { /// This case is interesting, because the fixed point for the loop doesn't /// introduce new abstractions. -fn clear(v: &mut Vec) { +pub fn clear(v: &mut Vec) { let mut i = 0; while i < v.len() { v[i] = 0; diff --git a/tests/src/loops_cfg.rs b/tests/src/loops_cfg.rs index 04cb851a..b814893f 100644 --- a/tests/src/loops_cfg.rs +++ b/tests/src/loops_cfg.rs @@ -1,8 +1,7 @@ //! This module contains loops whose main purpose is to test the CFG reconstruction. -#![allow(dead_code)] /// Simple test with a loop -fn test_loop1(max: u32) -> u32 { +pub fn test_loop1(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -15,7 +14,7 @@ fn test_loop1(max: u32) -> u32 { } /// Test with a loop and a break -fn test_loop2(max: u32) -> u32 { +pub fn test_loop2(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -30,7 +29,7 @@ fn test_loop2(max: u32) -> u32 { } /// Test with nested loops and continue to outer loops -fn test_loop3(max: u32) -> u32 { +pub fn test_loop3(max: u32) -> u32 { let mut i = 0; let mut j = 0; let mut s = 0; @@ -57,7 +56,7 @@ fn test_loop3(max: u32) -> u32 { /// sense, but it initially lead to strange results after control-flow reconstruction /// (with some code duplicata). #[allow(unused_assignments)] -fn test_loop4(max: u32) -> u32 { +pub fn test_loop4(max: u32) -> u32 { let mut i = 1; let mut j = 0; let mut s = 0; @@ -81,7 +80,7 @@ fn test_loop4(max: u32) -> u32 { /// Just checking we don't generate interleaved loops (with the inner loop /// using a break or a continue to the outer loop). -fn test_loop5(max: u32) -> u32 { +pub fn test_loop5(max: u32) -> u32 { let mut i = 0; let mut j = 0; let mut s = 0; @@ -99,7 +98,7 @@ fn test_loop5(max: u32) -> u32 { /// In this function, the loop has several exit candidates with a number of /// occurrences > 1. -fn test_loop6(max: u32) -> u32 { +pub fn test_loop6(max: u32) -> u32 { let mut i = 0; let mut s = 0; while i < max { @@ -117,7 +116,7 @@ fn test_loop6(max: u32) -> u32 { /// In this function, the loop is inside an `if ... then ... else ...`, so /// that the loop exit coincides with the `if ... then ... else ...` exit. -fn test_loop7(max: u32) -> u32 { +pub fn test_loop7(max: u32) -> u32 { let mut i = 0; let mut s = 0; if i < max { @@ -136,7 +135,7 @@ fn test_loop7(max: u32) -> u32 { s } -fn test_loops() { +pub fn test_loops() { let x = test_loop1(2); assert!(x == 2); let x = test_loop2(2); @@ -150,3 +149,21 @@ fn test_loops() { let x = test_loop6(2); assert!(x == 2); } + +/// This one is to check that the reconstructed CFG doesn't nest the two loops +/// (it can happen and be valid, but we of course don't want that). +pub fn nested_loops_enum(step_out: usize, step_in: usize) -> usize { + let mut s = 0; + + for _ in 0..128 { + s += 1; + } + + for _ in 0..(step_out) { + for _ in 0..(step_in) { + s += 1; + } + } + + s +} diff --git a/tests/src/matches.rs b/tests/src/matches.rs index 4bfd91b2..2d132c2e 100644 --- a/tests/src/matches.rs +++ b/tests/src/matches.rs @@ -1,7 +1,6 @@ //! This module tests the translation of matches. -#![allow(dead_code)] -enum E1 { +pub enum E1 { V1, V2, V3, @@ -35,7 +34,7 @@ enum E1 { /// main problem is that it would be difficult to make the distinction between /// a goto we need to ignore, and a "real" goto. /// Consequently, whenever branhces are fused, don't use `--no-code-duplication`. -fn test1(x: E1) -> bool { +pub fn test1(x: E1) -> bool { match x { E1::V1 | E1::V2 => true, E1::V3 => false, diff --git a/tests/src/matches_duplicate.rs b/tests/src/matches_duplicate.rs index 6b6fb809..6082c095 100644 --- a/tests/src/matches_duplicate.rs +++ b/tests/src/matches_duplicate.rs @@ -1,11 +1,10 @@ //! This module tests the translation of matches. -#![allow(dead_code)] -fn id(x: T) -> T { +pub fn id(x: T) -> T { x } -enum E2 { +pub enum E2 { V1(u32), V2(u32), V3, @@ -14,7 +13,7 @@ enum E2 { /// Testing matches where several branches are "fused". /// The following leads to code-duplication (we must thus deactivate /// code-duplication detection). -fn test2(x: E2) -> u32 { +pub fn test2(x: E2) -> u32 { match x { E2::V1(n) | E2::V2(n) => n, E2::V3 => 0, @@ -22,7 +21,7 @@ fn test2(x: E2) -> u32 { } /// Similar to test2 -fn test3(x: E2) -> u32 { +pub fn test3(x: E2) -> u32 { let y = match x { E2::V1(n) | E2::V2(n) => n, E2::V3 => 0, diff --git a/tests/src/nested_borrows.rs b/tests/src/nested_borrows.rs index 0cc08507..9b1094b8 100644 --- a/tests/src/nested_borrows.rs +++ b/tests/src/nested_borrows.rs @@ -1,15 +1,14 @@ //! This module contains functions with nested borrows in their signatures. -#![allow(dead_code)] -fn id_mut_mut<'a, 'b, T>(x: &'a mut &'b mut T) -> &'a mut &'b mut T { +pub fn id_mut_mut<'a, 'b, T>(x: &'a mut &'b mut T) -> &'a mut &'b mut T { x } -fn id_mut_pair<'a, T>(x: &'a mut (&'a mut T, u32)) -> &'a mut (&'a mut T, u32) { +pub fn id_mut_pair<'a, T>(x: &'a mut (&'a mut T, u32)) -> &'a mut (&'a mut T, u32) { x } -fn id_mut_pair_test1() { +pub fn id_mut_pair_test1() { let mut x: u32 = 0; let px = &mut x; let mut p = (px, 1); @@ -19,20 +18,22 @@ fn id_mut_pair_test1() { *pp1 = (&mut y, 3); } -fn id_mut_mut_pair<'a, T>(x: &'a mut &'a mut (&'a mut T, u32)) -> &'a mut &'a mut (&'a mut T, u32) { +pub fn id_mut_mut_pair<'a, T>( + x: &'a mut &'a mut (&'a mut T, u32), +) -> &'a mut &'a mut (&'a mut T, u32) { x } -fn id_mut_mut_mut_same<'a, T>(x: &'a mut &'a mut &'a mut u32) -> &'a mut &'a mut &'a mut u32 { +pub fn id_mut_mut_mut_same<'a, T>(x: &'a mut &'a mut &'a mut u32) -> &'a mut &'a mut &'a mut u32 { x } -fn id_borrow1<'a, 'b, 'c>(_x: &'a mut &'b u32, _y: &'a &'a mut u32) { +pub fn id_borrow1<'a, 'b, 'c>(_x: &'a mut &'b u32, _y: &'a &'a mut u32) { () } /// For symbolic execution: testing what happens with several abstractions. -fn id_mut_mut_test1() { +pub fn id_mut_mut_test1() { let mut x = 0; let mut px = &mut x; let ppx = &mut px; @@ -49,7 +50,7 @@ fn id_mut_mut_test1() { /// This case is a bit trickier, because we modify the borrow graph through /// a value returned by a function call. /// TODO: not supported! We overwrite a borrow in a returned value. -fn id_mut_mut_test2() { +pub fn id_mut_mut_test2() { let mut x = 0; let mut px = &mut x; let ppx = &mut px; @@ -72,7 +73,7 @@ fn id_mut_mut_test2() { /// For symbolic execution: testing what happens with several abstractions. /// See what happens when chaining function calls. /// TODO: not supported! We overwrite a borrow in a returned value. -fn id_mut_mut_test3() { +pub fn id_mut_mut_test3() { let mut x = 0; let mut px = &mut x; let ppx = &mut px; @@ -90,7 +91,7 @@ fn id_mut_mut_test3() { /// For symbolic execution: testing what happens with several abstractions. /// See what happens when chaining function calls. /// This one is slightly more complex than the previous one. -fn id_mut_mut_test4() { +pub fn id_mut_mut_test4() { let mut x = 0; let mut px = &mut x; let ppx = &mut px; diff --git a/tests/src/no_nested_borrows.rs b/tests/src/no_nested_borrows.rs index e12f6e4e..670dac41 100644 --- a/tests/src/no_nested_borrows.rs +++ b/tests/src/no_nested_borrows.rs @@ -1,10 +1,9 @@ //! This module doesn't contain **functions which use nested borrows in their //! signatures**, and doesn't contain functions with loops. -#![allow(dead_code)] pub struct Pair { - x: T1, - y: T2, + pub x: T1, + pub y: T2, } pub enum List { @@ -46,50 +45,62 @@ pub enum Sum { /// Testing unop simplification /// In debug mode, rust introduces an assertion before the negation. -fn neg_test(x: i32) -> i32 { +pub fn neg_test(x: i32) -> i32 { -x } /// Testing binop simplification /// In debug mode, rust inserts an assertion after the addition -fn add_test(x: u32, y: u32) -> u32 { +pub fn add_test(x: u32, y: u32) -> u32 { x + y } /// Testing binop simplification /// In debug mode, rust inserts an assertion after the substraction -fn subs_test(x: u32, y: u32) -> u32 { +pub fn subs_test(x: u32, y: u32) -> u32 { x - y } /// Testing binop simplification /// In debug mode, rust inserts an assertion before the division -fn div_test(x: u32, y: u32) -> u32 { +pub fn div_test(x: u32, y: u32) -> u32 { x / y } /// Testing binop simplification /// When using constants, rustc removes the unnecessary assertions (but /// only at a specific pass) -fn div_test1(x: u32) -> u32 { +pub fn div_test1(x: u32) -> u32 { x / 2 } /// Testing binop simplification -fn rem_test(x: u32, y: u32) -> u32 { +pub fn rem_test(x: u32, y: u32) -> u32 { x % y } -fn cast_test(x: u32) -> i32 { +pub fn mul_test(x: u32, y: u32) -> u32 { + x * y +} + +/* Checking the simplification of binop operations *inside* global constants. + + In release mode, the Rust compiler inserts additional checks inside constant + bodies. +*/ +pub const CONST0: usize = 1 + 1; +pub const CONST1: usize = 2 * 2; + +pub fn cast_test(x: u32) -> i32 { x as i32 } #[allow(unused_variables)] -fn test2() { +pub fn test2() { let x: u32 = 23; let y: u32 = 44; let z = x + y; - let p: Pair = Pair { x: x, y: z }; + let p: Pair = Pair { x, y: z }; let s: Sum = Sum::Right(true); let o: One = One::One(3); let e0 = EmptyEnum::Empty; @@ -97,7 +108,7 @@ fn test2() { let enum0 = Enum::Variant1; } -fn get_max(x: u32, y: u32) -> u32 { +pub fn get_max(x: u32, y: u32) -> u32 { if x >= y { x } else { @@ -105,21 +116,21 @@ fn get_max(x: u32, y: u32) -> u32 { } } -fn test3() { +pub fn test3() { let x = get_max(4, 3); let y = get_max(10, 11); let z = x + y; assert!(z == 15); } -fn test_neg1() { +pub fn test_neg1() { let x: i32 = 3; let y = -x; assert!(y == -3); } /// Testing nested references. -fn refs_test1() { +pub fn refs_test1() { let mut x = 0; let mut px = &mut x; let ppx = &mut px; @@ -130,7 +141,7 @@ fn refs_test1() { assert!(x == 1); } -fn refs_test2() { +pub fn refs_test2() { let mut x = 0; let mut y = 1; let mut px = &mut x; @@ -146,12 +157,12 @@ fn refs_test2() { /// Box creation #[allow(unused_variables)] -fn test_list1() { +pub fn test_list1() { let l: List = List::Cons(0, Box::new(List::Nil)); } /// Box deref -fn test_box1() { +pub fn test_box1() { use std::ops::Deref; use std::ops::DerefMut; let mut b: Box = Box::new(0); @@ -161,13 +172,13 @@ fn test_box1() { assert!(*x == 1); } -fn copy_int(x: i32) -> i32 { +pub fn copy_int(x: i32) -> i32 { x } /// Just checking the parameters given to unreachable /// Rk.: the input parameter prevents using the function as a unit test. -fn test_unreachable(b: bool) { +pub fn test_unreachable(b: bool) { if b { unreachable!(); } @@ -175,34 +186,34 @@ fn test_unreachable(b: bool) { /// Just checking the parameters given to panic /// Rk.: the input parameter prevents using the function as a unit test. -fn test_panic(b: bool) { +pub fn test_panic(b: bool) { if b { panic!("Panicked!"); } } // Just testing that shared loans are correctly handled -fn test_copy_int() { +pub fn test_copy_int() { let x = 0; let px = &x; let y = copy_int(x); assert!(*px == y); } -fn is_cons(l: &List) -> bool { +pub fn is_cons(l: &List) -> bool { match l { List::Cons(_, _) => true, List::Nil => false, } } -fn test_is_cons() { +pub fn test_is_cons() { let l: List = List::Cons(0, Box::new(List::Nil)); assert!(is_cons(&l)); } -fn split_list(l: List) -> (T, List) { +pub fn split_list(l: List) -> (T, List) { match l { List::Cons(hd, tl) => (hd, *tl), _ => panic!(), @@ -210,26 +221,26 @@ fn split_list(l: List) -> (T, List) { } #[allow(unused_variables)] -fn test_split_list() { +pub fn test_split_list() { let l: List = List::Cons(0, Box::new(List::Nil)); let (hd, tl) = split_list(l); assert!(hd == 0); } -fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T { +pub fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T { if b { - return x; + x } else { - return y; + y } } -fn choose_test() { +pub fn choose_test() { let mut x = 0; let mut y = 0; let z = choose(true, &mut x, &mut y); - *z = *z + 1; + *z += 1; assert!(*z == 1); // drop(z) assert!(x == 1); @@ -237,17 +248,17 @@ fn choose_test() { } /// Test with a char literal - testing serialization -fn test_char() -> char { +pub fn test_char() -> char { 'a' } /// Mutually recursive types -enum Tree { +pub enum Tree { Leaf(T), Node(T, NodeElem, Box>), } -enum NodeElem { +pub enum NodeElem { Cons(Box>, Box>), Nil, } @@ -257,7 +268,7 @@ enum NodeElem { // because we don't use fuel in this case). /// Mutually recursive functions -fn even(x: u32) -> bool { +pub fn even(x: u32) -> bool { if x == 0 { true } else { @@ -265,7 +276,7 @@ fn even(x: u32) -> bool { } } -fn odd(x: u32) -> bool { +pub fn odd(x: u32) -> bool { if x == 0 { false } else { @@ -273,7 +284,7 @@ fn odd(x: u32) -> bool { } } -fn test_even_odd() { +pub fn test_even_odd() { assert!(even(0)); assert!(even(4)); assert!(odd(1)); @@ -281,17 +292,15 @@ fn test_even_odd() { } */ +#[allow(clippy::needless_lifetimes)] pub fn list_length<'a, T>(l: &'a List) -> u32 { match l { - List::Nil => { - return 0; - } - List::Cons(_, l1) => { - return 1 + list_length(l1); - } + List::Nil => 0, + List::Cons(_, l1) => 1 + list_length(l1), } } +#[allow(clippy::needless_lifetimes)] pub fn list_nth_shared<'a, T>(l: &'a List, i: u32) -> &'a T { match l { List::Nil => { @@ -299,54 +308,51 @@ pub fn list_nth_shared<'a, T>(l: &'a List, i: u32) -> &'a T { } List::Cons(x, tl) => { if i == 0 { - return x; + x } else { - return list_nth_shared(tl, i - 1); + list_nth_shared(tl, i - 1) } } } } +#[allow(clippy::needless_lifetimes)] pub fn list_nth_mut<'a, T>(l: &'a mut List, i: u32) -> &'a mut T { - // (i) match l { List::Nil => { panic!() } List::Cons(x, tl) => { - // (ii) if i == 0 { - return x; // (iii) + x } else { - // (iv) - return list_nth_mut(tl, i - 1); + list_nth_mut(tl, i - 1) } } } } /// In-place list reversal - auxiliary function -fn list_rev_aux<'a, T>(li: List, mut lo: List) -> List { +pub fn list_rev_aux(li: List, mut lo: List) -> List { match li { - List::Nil => { - return lo; - } + List::Nil => lo, List::Cons(hd, mut tl) => { let next = *tl; *tl = lo; lo = List::Cons(hd, tl); - return list_rev_aux(next, lo); + list_rev_aux(next, lo) } } } /// In-place list reversal +#[allow(clippy::needless_lifetimes)] pub fn list_rev<'a, T>(l: &'a mut List) { let li = std::mem::replace(l, List::Nil); *l = list_rev_aux(li, List::Nil); } -fn test_list_functions() { +pub fn test_list_functions() { let mut ls = List::Cons( 0, Box::new(List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))))), @@ -381,28 +387,28 @@ pub fn id_mut_pair4<'a, 'b, T1, T2>(p: (&'a mut T1, &'b mut T2)) -> (&'a mut T1, /// Testing constants (some constants are hard to retrieve from MIR, because /// they are compiled to very low values). /// We resort to the following structure to make rustc generate constants... -struct StructWithTuple { +pub struct StructWithTuple { p: (T1, T2), } -fn new_tuple1() -> StructWithTuple { +pub fn new_tuple1() -> StructWithTuple { StructWithTuple { p: (1, 2) } } -fn new_tuple2() -> StructWithTuple { +pub fn new_tuple2() -> StructWithTuple { StructWithTuple { p: (1, 2) } } -fn new_tuple3() -> StructWithTuple { +pub fn new_tuple3() -> StructWithTuple { StructWithTuple { p: (1, 2) } } /// Similar to [StructWithTuple] -struct StructWithPair { +pub struct StructWithPair { p: Pair, } -fn new_pair1() -> StructWithPair { +pub fn new_pair1() -> StructWithPair { // This actually doesn't make rustc generate a constant... // I guess it only happens for tuples. StructWithPair { @@ -410,7 +416,7 @@ fn new_pair1() -> StructWithPair { } } -fn test_constants() { +pub fn test_constants() { assert!(new_tuple1().p.0 == 1); assert!(new_tuple2().p.0 == 1); assert!(new_tuple3().p.0 == 1); @@ -419,7 +425,7 @@ fn test_constants() { /// This assignment is trickier than it seems #[allow(unused_assignments)] -fn test_weird_borrows1() { +pub fn test_weird_borrows1() { let mut x = 0; let mut px = &mut x; // Context: @@ -429,14 +435,14 @@ fn test_weird_borrows1() { px = &mut (*px); } -fn test_mem_replace(px: &mut u32) { +pub fn test_mem_replace(px: &mut u32) { let y = std::mem::replace(px, 1); assert!(y == 0); *px = 2; } /// Check that matching on borrowed values works well. -fn test_shared_borrow_bool1(b: bool) -> u32 { +pub fn test_shared_borrow_bool1(b: bool) -> u32 { // Create a shared borrow of b let _pb = &b; // Match on b @@ -449,7 +455,7 @@ fn test_shared_borrow_bool1(b: bool) -> u32 { /// Check that matching on borrowed values works well. /// Testing the concrete execution here. -fn test_shared_borrow_bool2() -> u32 { +pub fn test_shared_borrow_bool2() -> u32 { let b = true; // Create a shared borrow of b let _pb = &b; @@ -464,7 +470,7 @@ fn test_shared_borrow_bool2() -> u32 { /// Check that matching on borrowed values works well. /// In case of enumerations, we need to strip the outer loans before evaluating /// the discriminant. -fn test_shared_borrow_enum1(l: List) -> u32 { +pub fn test_shared_borrow_enum1(l: List) -> u32 { // Create a shared borrow of l let _pl = &l; // Match on l - must ignore the shared loan @@ -476,7 +482,7 @@ fn test_shared_borrow_enum1(l: List) -> u32 { /// Check that matching on borrowed values works well. /// Testing the concrete execution here. -fn test_shared_borrow_enum2() -> u32 { +pub fn test_shared_borrow_enum2() -> u32 { let l: List = List::Nil; // Create a shared borrow of l let _pl = &l; diff --git a/tests/src/paper.rs b/tests/src/paper.rs index 837cf91a..156f13ab 100644 --- a/tests/src/paper.rs +++ b/tests/src/paper.rs @@ -1,19 +1,18 @@ //! The examples from the ICFP submission, all in one place. -#![allow(dead_code)] // 2.1 -fn ref_incr(x: &mut i32) { +pub fn ref_incr(x: &mut i32) { *x = *x + 1; } -fn test_incr() { +pub fn test_incr() { let mut x = 0i32; ref_incr(&mut x); assert!(x == 1); } // 2.2 -fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T { +pub fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T { if b { return x; } else { @@ -21,7 +20,7 @@ fn choose<'a, T>(b: bool, x: &'a mut T, y: &'a mut T) -> &'a mut T { } } -fn test_choose() { +pub fn test_choose() { let mut x = 0; let mut y = 0; let z = choose(true, &mut x, &mut y); @@ -33,14 +32,14 @@ fn test_choose() { // 2.3 -enum List { +pub enum List { Cons(T, Box>), Nil, } use List::Cons; use List::Nil; -fn list_nth_mut<'a, T>(l: &'a mut List, i: u32) -> &'a mut T { +pub fn list_nth_mut<'a, T>(l: &'a mut List, i: u32) -> &'a mut T { match l { Nil => { panic!() @@ -55,7 +54,7 @@ fn list_nth_mut<'a, T>(l: &'a mut List, i: u32) -> &'a mut T { } } -fn sum(l: &List) -> i32 { +pub fn sum(l: &List) -> i32 { match l { Nil => { return 0; @@ -66,7 +65,7 @@ fn sum(l: &List) -> i32 { } } -fn test_nth() { +pub fn test_nth() { let mut l = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); let x = list_nth_mut(&mut l, 2); *x = *x + 1; @@ -74,7 +73,7 @@ fn test_nth() { } // 4.3 -fn call_choose(mut p: (u32, u32)) -> u32 { +pub fn call_choose(mut p: (u32, u32)) -> u32 { let px = &mut p.0; let py = &mut p.1; let pz = choose(true, px, py); diff --git a/tests/src/traits.rs b/tests/src/traits.rs new file mode 100644 index 00000000..53f42b13 --- /dev/null +++ b/tests/src/traits.rs @@ -0,0 +1,298 @@ +pub trait BoolTrait { + // Required method + fn get_bool(&self) -> bool; + + // Provided method + fn ret_true(&self) -> bool { + true + } +} + +impl BoolTrait for bool { + fn get_bool(&self) -> bool { + *self + } +} + +pub fn test_bool_trait_bool(x: bool) -> bool { + x.get_bool() && x.ret_true() +} + +#[allow(clippy::redundant_pattern_matching)] +impl BoolTrait for Option { + fn get_bool(&self) -> bool { + match self { + Option::Some(_) => true, + Option::None => false, + } + } +} + +pub fn test_bool_trait_option(x: Option) -> bool { + x.get_bool() && x.ret_true() +} + +pub fn test_bool_trait(x: T) -> bool { + x.get_bool() +} + +pub trait ToU64 { + fn to_u64(self) -> u64; +} + +impl ToU64 for u64 { + fn to_u64(self) -> u64 { + self + } +} + +impl ToU64 for (A, A) { + fn to_u64(self) -> u64 { + self.0.to_u64() + self.1.to_u64() + } +} + +pub fn f(x: (T, T)) -> u64 { + x.to_u64() +} + +pub fn g(x: (T, T)) -> u64 +where + (T, T): ToU64, +{ + x.to_u64() +} + +pub fn h0(x: u64) -> u64 { + x.to_u64() +} + +pub struct Wrapper { + x: T, +} + +impl ToU64 for Wrapper { + fn to_u64(self) -> u64 { + self.x.to_u64() + } +} + +pub fn h1(x: Wrapper) -> u64 { + x.to_u64() +} + +pub fn h2(x: Wrapper) -> u64 { + x.to_u64() +} + +pub trait ToType { + fn to_type(self) -> T; +} + +impl ToType for u64 { + fn to_type(self) -> bool { + self > 0 + } +} + +pub trait OfType { + fn of_type>(x: T) -> Self + where + Self: std::marker::Sized; +} + +pub fn h3>(y: T2) -> T1 { + T1::of_type(y) +} + +// Checking what happens if we move trait clauses from a method to its enclosing block +pub trait OfTypeBis> +where + Self: std::marker::Sized, +{ + fn of_type(x: T) -> Self + where + Self: std::marker::Sized; +} + +pub fn h4, T2: ToType>(y: T2) -> T1 { + T1::of_type(y) +} + +pub struct TestType(T); + +// Checking what happens with nested blocks +impl TestType { + pub fn test(&self, x: T) -> bool { + struct TestType1(u64); + trait TestTrait { + fn test(&self) -> bool; + } + + // Remark: we can't write: impl TestTrait for TestType, + // we have to use a *local* parameter (can't use the outer T). + // In other words: the parameters used in the items inside + // an impl must be bound by the impl block (can't come from outer + // blocks). + + impl TestTrait for TestType1 { + fn test(&self) -> bool { + self.0 > 1 + } + } + + let x = x.to_u64(); + let y = TestType1(0); + x > 0 && y.test() + } +} + +pub struct BoolWrapper(pub bool); + +impl ToType for BoolWrapper +where + bool: ToType, +{ + fn to_type(self) -> T { + self.0.to_type() + } +} + +pub trait WithConstTy { + const LEN1: usize; + // Testing default values + const LEN2: usize = 32; + + type V; + type W: ToU64; + + // Below: we can't use [Self::Len1] in the type of the array. + // Probably because of dyn traits... + fn f(x: &mut Self::W, y: &[u8; LEN]); +} + +impl WithConstTy<32> for bool { + const LEN1: usize = 12; + + type V = u8; + type W = u64; + + fn f(_: &mut Self::W, _: &[u8; 32]) {} +} + +pub fn use_with_const_ty1>() -> usize { + H::LEN1 +} + +pub fn use_with_const_ty2>(_: H::W) {} + +pub fn use_with_const_ty3>(x: H::W) -> u64 { + x.to_u64() +} + +pub fn test_where1<'a, T: 'a>(_x: &'a T) {} +pub fn test_where2>(_x: T::V) {} + +// Below: testing super traits. +// +// Actually, this comes for free: ChildTrait : ParentTrait just adds a trait +// clause for Self: `Self : ParentTrait`. +pub trait ParentTrait0 { + type W; + fn get_name(&self) -> String; + fn get_w(&self) -> Self::W; +} +pub trait ParentTrait1 {} +pub trait ChildTrait: ParentTrait0 + ParentTrait1 {} + +// But we still need to correctly reference the traits +pub fn test_child_trait1(x: &T) -> String { + x.get_name() +} + +pub fn test_child_trait2(x: &T) -> T::W { + x.get_w() +} + +// Checking if the order has an importance (we use U::W before we declare that +// U:ParentTrait0) +pub fn order1, U: ParentTrait0>() {} + +/* */ +pub trait ChildTrait1: ParentTrait1 {} + +impl ParentTrait1 for usize {} +impl ChildTrait1 for usize {} + +/* [IntoIterator] is interesting because of the condition [Item = Self::Item] +for the [IntoIter] associated type. */ +pub trait Iterator { + type Item; +} + +pub trait IntoIterator { + type Item; + type IntoIter: Iterator; + + // Required method + fn into_iter(self) -> Self::IntoIter; +} + +/* The traits below are inspired by [Try] and [FromResidual]. + + The reference to `Self as Try` in the `FromResidual` clause used to + cause a bug. +*/ +trait Try: FromResidual<::Residual> { + type Residual; +} + +trait FromResidual {} + +pub trait WithTarget { + type Target; +} + +pub trait ParentTrait2 { + type U: WithTarget; +} + +pub trait ChildTrait2: ParentTrait2 { + fn convert(x: Self::U) -> ::Target; +} + +impl WithTarget for u32 { + type Target = u32; +} + +impl ParentTrait2 for u32 { + type U = u32; +} + +impl ChildTrait2 for u32 { + fn convert(x: u32) -> u32 { + x + } +} + +/* +// This one requires a lot of traits +pub fn test_enumerate(x: usize) { + for _ in 0..x {} +} +*/ + +/* Custom function pointers */ +pub trait CFnOnce { + type Output; + + fn call_once(self, args: Args) -> Self::Output; +} + +pub trait CFnMut: CFnOnce { + fn call_mut(&mut self, args: Args) -> Self::Output; +} + +pub trait CFn: CFnMut { + fn call_mut(&self, args: Args) -> Self::Output; +} diff --git a/tests/src/traits_special.rs b/tests/src/traits_special.rs new file mode 100644 index 00000000..63ac08d0 --- /dev/null +++ b/tests/src/traits_special.rs @@ -0,0 +1,26 @@ +// The example below (in particular the impl block) is peculiar with regards +// to the treatment of regions. In particular, when we translate the implementation +// of `from` to LLBC, we get something which looks like this: +// ``` +// +// fn crate::{bool}::from<@R0, @R1>(@1: &@R1 (bool)) -> +// core::result::Result::Error> { +// // ^^^ +// // HERE +// ... // omitted +// } +// ``` +pub trait From { + type Error; + fn from(v: T) -> Result + where + Self: std::marker::Sized; +} + +impl From<&bool> for bool { + type Error = (); + + fn from(v: &bool) -> Result { + Ok(*v) + } +}