[erl-frontend] Support records

Summary:
Add support for Erlang records, including:
- Parsing record definitions, storing info in environment
- Record index expressions and patterns
- Record creation expression, including initializers
- Record update expressions and patterns
- Record field access expressions
- Pulse models

The key idea is to translate records to tagged tuples (just as Erlang does under the hood), but this requires some bookkeeping to figure out which field maps to which tuple element.

Reviewed By: rgrig

Differential Revision: D29991819

fbshipit-source-id: a1c713b41
master
Akos Hajdu 3 years ago committed by Facebook GitHub Bot
parent d48855913c
commit 5f140ed91b

@ -27,4 +27,7 @@ let cons_head = "head"
let cons_tail = "tail" let cons_tail = "tail"
let tuple_field_names size = List.init size ~f:(Printf.sprintf "elem%d") let tuple_elem i = Printf.sprintf "elem%d" i
(* Tuple element indexing is one based *)
let tuple_field_names size = List.init size ~f:(fun i -> tuple_elem (i + 1))

@ -136,13 +136,16 @@ and catch_pattern = {exception_: exception_; pattern: pattern; variable: string}
(** {2 S8.1: Module declarations and forms} *) (** {2 S8.1: Module declarations and forms} *)
(* TODO: Add records, types, and specs. *) (* TODO: Add types, and specs. *)
type record_field = {field_name: string; initializer_: expression option} [@@deriving sexp_of]
type simple_form = type simple_form =
| Export of function_ list | Export of function_ list
| Import of {module_name: string; functions: function_ list} | Import of {module_name: string; functions: function_ list}
| Module of string | Module of string
| File of {path: string} | File of {path: string}
| Function of {function_: function_; clauses: case_clause list} | Function of {function_: function_; clauses: case_clause list}
| Record of {name: string; fields: record_field list}
[@@deriving sexp_of] [@@deriving sexp_of]
type form = {line: line; simple_form: simple_form} [@@deriving sexp_of] type form = {line: line; simple_form: simple_form} [@@deriving sexp_of]

@ -479,6 +479,16 @@ let to_function json : Ast.function_ option =
unknown "function" json unknown "function" json
let to_record_field json : Ast.record_field option =
match json with
| `List [`String "record_field"; _; `List [`String "atom"; _; `String field_name]; expr] ->
Some {Ast.field_name; initializer_= to_expression expr}
| `List [`String "record_field"; _; `List [`String "atom"; _; `String field_name]] ->
Some {Ast.field_name; initializer_= None}
| _ ->
unknown "record_field" json
let to_line_form json : Ast.form option = let to_line_form json : Ast.form option =
let form line simple_form : Ast.form option = Some {line; simple_form} in let form line simple_form : Ast.form option = Some {line; simple_form} in
match json with match json with
@ -502,8 +512,12 @@ let to_line_form json : Ast.form option =
let function_ : Ast.function_reference = FunctionName function_ in let function_ : Ast.function_reference = FunctionName function_ in
let function_ : Ast.function_ = {module_= ModuleMissing; function_; arity} in let function_ : Ast.function_ = {module_= ModuleMissing; function_; arity} in
form line (Function {function_; clauses}) form line (Function {function_; clauses})
| `List [`String "attribute"; anno; `String "record"; `List [`String name; fields]] ->
let* line = to_line anno in
let* field_list = to_list ~f:to_record_field fields in
form line (Record {name; fields= field_list})
| `List [`String "attribute"; _anno; `String _unknown_attribute; _] -> | `List [`String "attribute"; _anno; `String _unknown_attribute; _] ->
(* TODO: handle types (spec, record, ...) *) (* TODO: handle types (spec, ...) *)
None None
| `List [`String "eof"; _] -> | `List [`String "eof"; _] ->
None None

@ -37,10 +37,16 @@ type absent = Absent
type 'a present = Present of 'a type 'a present = Present of 'a
type record_field_info = {index: int; initializer_: Ast.expression option} [@@deriving sexp_of]
type record_info = {field_names: string list; field_info: record_field_info String.Map.t}
[@@deriving sexp_of]
type ('procdesc, 'result) environment = type ('procdesc, 'result) environment =
{ current_module: module_name (** used to qualify function names *) { current_module: module_name (** used to qualify function names *)
; exports: UnqualifiedFunction.Set.t (** used to determine public/private access *) ; exports: UnqualifiedFunction.Set.t (** used to determine public/private access *)
; imports: module_name UnqualifiedFunction.Map.t (** used to resolve function names *) ; imports: module_name UnqualifiedFunction.Map.t (** used to resolve function names *)
; records: record_info String.Map.t (** used to get fields, indexes and initializers *)
; location: Location.t (** used to tag nodes and instructions being created *) ; location: Location.t (** used to tag nodes and instructions being created *)
; procdesc: ('procdesc[@sexp.opaque]) ; procdesc: ('procdesc[@sexp.opaque])
; result: ('result[@sexp.opaque]) } ; result: ('result[@sexp.opaque]) }
@ -51,6 +57,7 @@ let get_environment module_ =
{ current_module= Printf.sprintf "%s:unknown_module" __FILE__ { current_module= Printf.sprintf "%s:unknown_module" __FILE__
; exports= UnqualifiedFunction.Set.empty ; exports= UnqualifiedFunction.Set.empty
; imports= UnqualifiedFunction.Map.empty (* TODO: auto-import from module "erlang" *) ; imports= UnqualifiedFunction.Map.empty (* TODO: auto-import from module "erlang" *)
; records= String.Map.empty
; location= Location.dummy ; location= Location.dummy
; procdesc= Absent ; procdesc= Absent
; result= Absent } ; result= Absent }
@ -72,6 +79,26 @@ let get_environment module_ =
in in
let imports = List.fold ~init:env.imports ~f functions in let imports = List.fold ~init:env.imports ~f functions in
{env with imports} {env with imports}
| Record {name; fields} -> (
let process_one_field one_index map (one_field : Ast.record_field) =
(* Tuples are indexed from 1 and the first one is the name, hence start from 2 *)
match
Map.add ~key:one_field.field_name
~data:{index= one_index + 2; initializer_= one_field.initializer_}
map
with
| `Ok map ->
map
| `Duplicate ->
L.die InternalError "repeated field in record: %s" one_field.field_name
in
let field_info = List.foldi ~init:String.Map.empty ~f:process_one_field fields in
let field_names = List.map ~f:(fun (rf : Ast.record_field) -> rf.field_name) fields in
match Map.add ~key:name ~data:{field_names; field_info} env.records with
| `Ok records ->
{env with records}
| `Duplicate ->
L.die InternalError "repeated record: %s" name )
| Module current_module -> | Module current_module ->
{env with current_module} {env with current_module}
| File {path} -> | File {path} ->
@ -216,6 +243,15 @@ module Block = struct
let exit_success = Node.make_load env id e typ in let exit_success = Node.make_load env id e typ in
let exit_failure = Node.make_nop env in let exit_failure = Node.make_nop env in
{start= exit_success; exit_success; exit_failure} {start= exit_success; exit_success; exit_failure}
(** Make a branch based on the condition: go to success if true, go to failure if false *)
let make_branch env condition =
let start = Node.make_nop env in
let exit_success = Node.make_if env true condition in
let exit_failure = Node.make_if env false condition in
start |~~> [exit_success; exit_failure] ;
{start; exit_success; exit_failure}
end end
let has_type env ~result ~value (name : ErlangTypeName.t) : Sil.instr = let has_type env ~result ~value (name : ErlangTypeName.t) : Sil.instr =
@ -233,6 +269,13 @@ let has_type env ~result ~value (name : ErlangTypeName.t) : Sil.instr =
Call ((result, Typ.mk (Tint IBool)), fun_exp, args, env.location, CallFlags.default) Call ((result, Typ.mk (Tint IBool)), fun_exp, args, env.location, CallFlags.default)
let translate_atom_literal (atom : string) : Exp.t =
(* With this hack, an atom may accidentaly be considered equal to an unrelated integer.
The [lsl] below makes this less likely. Proper fix is TODO (T93513105). *)
let hash = String.hash atom lsl 16 in
Exp.Const (Cint (IntLit.of_int hash))
(** If the pattern-match succeeds, then the [exit_success] node is reached and the pattern variables (** If the pattern-match succeeds, then the [exit_success] node is reached and the pattern variables
are storing the corresponding values; otherwise, the [exit_failure] node is reached. *) are storing the corresponding values; otherwise, the [exit_failure] node is reached. *)
let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} : Block.t = let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} : Block.t =
@ -267,14 +310,12 @@ let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} :
wrong_type_node |~~> [exit_failure] ; wrong_type_node |~~> [exit_failure] ;
submatcher.exit_failure |~~> [exit_failure] ; submatcher.exit_failure |~~> [exit_failure] ;
{start; exit_success= submatcher.exit_success; exit_failure} {start; exit_success= submatcher.exit_success; exit_failure}
| Literal (Atom atom) ->
let e = translate_atom_literal atom in
Block.make_branch env (Exp.BinOp (Eq, Var value, e))
| Literal (Int i) -> | Literal (Int i) ->
let e = Exp.Const (Cint (IntLit.of_string i)) in let e = Exp.Const (Cint (IntLit.of_string i)) in
let cond = Exp.BinOp (Eq, Var value, e) in Block.make_branch env (Exp.BinOp (Eq, Var value, e))
let start = Node.make_nop env in
let exit_success = Node.make_if env true cond in
let exit_failure = Node.make_if env false cond in
start |~~> [exit_success; exit_failure] ;
{start; exit_success; exit_failure}
| Nil -> | Nil ->
let id = Ident.create_fresh Ident.knormal in let id = Ident.create_fresh Ident.knormal in
let start = Node.make_stmt env [has_type env ~result:id ~value Nil] in let start = Node.make_stmt env [has_type env ~result:id ~value Nil] in
@ -282,6 +323,60 @@ let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} :
let exit_failure = Node.make_if env false (Var id) in let exit_failure = Node.make_if env false (Var id) in
start |~~> [exit_success; exit_failure] ; start |~~> [exit_success; exit_failure] ;
{start; exit_success; exit_failure} {start; exit_success; exit_failure}
| RecordIndex {name; field} -> (
match String.Map.find env.records name with
| None ->
L.debug Capture Verbose "@[Unknown record %s@." name ;
Block.make_failure env
| Some record_info ->
let field_info = String.Map.find_exn record_info.field_info field in
let index_expr = Exp.Const (Cint (IntLit.of_int field_info.index)) in
Block.make_branch env (Exp.BinOp (Eq, Var value, index_expr)) )
| RecordUpdate {name; updates; _} -> (
(* Match the type and the record name *)
match String.Map.find env.records name with
| None ->
L.debug Capture Verbose "@[Unknown record %s@." name ;
Block.make_failure env
| Some record_info ->
let tuple_size = 1 + List.length record_info.field_names in
let tuple_typ : ErlangTypeName.t = Tuple tuple_size in
let is_right_type_id = Ident.create_fresh Ident.knormal in
let start = Node.make_stmt env [has_type env ~result:is_right_type_id ~value tuple_typ] in
let right_type_node = Node.make_if env true (Var is_right_type_id) in
let wrong_type_node = Node.make_if env false (Var is_right_type_id) in
let name_id = Ident.create_fresh Ident.knormal in
let name_load = load_field name_id (ErlangTypeName.tuple_elem 1) tuple_typ in
let unpack_node = Node.make_stmt env [name_load] in
let name_cond = Exp.BinOp (Eq, Var name_id, translate_atom_literal name) in
let right_name_node = Node.make_if env true name_cond in
let wrong_name_node = Node.make_if env false name_cond in
let exit_failure = Node.make_nop env in
start |~~> [right_type_node; wrong_type_node] ;
right_type_node |~~> [unpack_node] ;
unpack_node |~~> [right_name_node; wrong_name_node] ;
wrong_type_node |~~> [exit_failure] ;
wrong_name_node |~~> [exit_failure] ;
let record_name_matcher : Block.t = {start; exit_success= right_name_node; exit_failure} in
(* Match each specified field *)
let make_one_field_matcher (one_update : Ast.record_update) =
match one_update.field with
| Some name ->
let field_info = String.Map.find_exn record_info.field_info name in
let value_id = Ident.create_fresh Ident.knormal in
let tuple_elem = ErlangTypeName.tuple_elem field_info.index in
let load_instr = load_field value_id tuple_elem tuple_typ in
let unpack_node = Node.make_stmt env [load_instr] in
let submatcher = translate_pattern env value_id one_update.expression in
unpack_node |~~> [submatcher.start] ;
{ Block.start= unpack_node
; exit_success= submatcher.exit_success
; exit_failure= submatcher.exit_failure }
| None ->
Block.make_success env
in
let record_field_matchers = List.map ~f:make_one_field_matcher updates in
Block.all env (record_name_matcher :: record_field_matchers) )
| Tuple exprs -> | Tuple exprs ->
let is_right_type_id = Ident.create_fresh Ident.knormal in let is_right_type_id = Ident.create_fresh Ident.knormal in
let tuple_typ : ErlangTypeName.t = Tuple (List.length exprs) in let tuple_typ : ErlangTypeName.t = Tuple (List.length exprs) in
@ -315,12 +410,8 @@ let rec translate_pattern env (value : Ident.t) {Ast.line; simple_expression} :
let expr_block = let expr_block =
translate_expression {env with result= Present (Exp.Var id)} {Ast.line; simple_expression} translate_expression {env with result= Present (Exp.Var id)} {Ast.line; simple_expression}
in in
let cond = Exp.BinOp (Eq, Var value, Var id) in let branch_block = Block.make_branch env (Exp.BinOp (Eq, Var value, Var id)) in
let start = Node.make_nop env in Block.all env [expr_block; branch_block]
let exit_success = Node.make_if env true cond in
let exit_failure = Node.make_if env false cond in
start |~~> [exit_success; exit_failure] ;
Block.all env [expr_block; {start; exit_success; exit_failure}]
| Variable vname when String.equal vname "_" -> | Variable vname when String.equal vname "_" ->
Block.make_success env Block.make_success env
| Variable vname -> | Variable vname ->
@ -380,6 +471,11 @@ and translate_expression env {Ast.line; simple_expression} =
let ret_var = let ret_var =
match result with Exp.Var ret_var -> ret_var | _ -> Ident.create_fresh Ident.knormal match result with Exp.Var ret_var -> ret_var | _ -> Ident.create_fresh Ident.knormal
in in
let load_field id field expr typ : Sil.instr =
(* x=value.field *)
let field = Fieldname.make (ErlangType typ) field in
Load {id; e= Lfield (expr, field, typ_of_name typ); root_typ= any; typ= any; loc= env.location}
in
let expression_block : Block.t = let expression_block : Block.t =
match simple_expression with match simple_expression with
| BinaryOperator (e1, op, e2) -> ( | BinaryOperator (e1, op, e2) -> (
@ -526,12 +622,7 @@ and translate_expression env {Ast.line; simple_expression} =
let blocks = {blocks with exit_failure= crash_node} in let blocks = {blocks with exit_failure= crash_node} in
blocks blocks
| Literal (Atom atom) -> | Literal (Atom atom) ->
let hash = let e = translate_atom_literal atom in
(* With this hack, an atom may accidentaly be considered equal to an unrelated integer.
The [lsl] below makes this less likely. Proper fix is TODO (T93513105). *)
String.hash atom lsl 16
in
let e = Exp.Const (Cint (IntLit.of_int hash)) in
Block.make_load env ret_var e any Block.make_load env ret_var e any
| Literal (Int i) -> | Literal (Int i) ->
let e = Exp.Const (Cint (IntLit.of_string i)) in let e = Exp.Const (Cint (IntLit.of_string i)) in
@ -550,6 +641,114 @@ and translate_expression env {Ast.line; simple_expression} =
let fun_exp = Exp.Const (Cfun BuiltinDecl.__erlang_make_nil) in let fun_exp = Exp.Const (Cfun BuiltinDecl.__erlang_make_nil) in
let instruction = Sil.Call ((ret_var, any), fun_exp, [], env.location, CallFlags.default) in let instruction = Sil.Call ((ret_var, any), fun_exp, [], env.location, CallFlags.default) in
Block.make_instruction env [instruction] Block.make_instruction env [instruction]
| RecordAccess {record; name; field} -> (
(* TODO: check for badrecord T97040801 *)
let record_id = Ident.create_fresh Ident.knormal in
let record_block =
let result = Present (Exp.Var record_id) in
translate_expression {env with result} record
in
(* Under the hood, a record is a tagged tuple, the first element is the name,
and then the fields follow in the order as in the record definition. *)
match String.Map.find env.records name with
| None ->
L.debug Capture Verbose "@[Unknown record %s@." name ;
Block.make_success env
| Some record_info ->
let field_info = String.Map.find_exn record_info.field_info field in
let field_no = field_info.index in
let tuple_typ : ErlangTypeName.t = Tuple (1 + List.length record_info.field_names) in
let field_load =
load_field ret_var (ErlangTypeName.tuple_elem field_no) (Var record_id) tuple_typ
in
let load_block = Block.make_instruction env [field_load] in
Block.all env [record_block; load_block] )
| RecordIndex {name; field} -> (
match String.Map.find env.records name with
| None ->
L.debug Capture Verbose "@[Unknown record %s@." name ;
Block.make_success env
| Some record_info ->
let field_info = String.Map.find_exn record_info.field_info field in
let expr = Exp.Const (Cint (IntLit.of_int field_info.index)) in
Block.make_load env ret_var expr any )
| RecordUpdate {record; name; updates} -> (
(* Under the hood, a record is a tagged tuple, the first element is the name,
and then the fields follow in the order as in the record definition. *)
match String.Map.find env.records name with
| None ->
L.debug Capture Verbose "@[Unknown record %s@." name ;
Block.make_success env
| Some record_info ->
let tuple_typ : ErlangTypeName.t = Tuple (1 + List.length record_info.field_names) in
(* First collect all the fields that are updated *)
let collect_updates map (one_update : Ast.record_update) =
match one_update.field with
| Some name ->
Map.add_exn ~key:name ~data:one_update.expression map
| None ->
(* '_' stands for 'everything else' *)
Map.add_exn ~key:"_" ~data:one_update.expression map
in
let updates_map = List.fold ~init:String.Map.empty ~f:collect_updates updates in
(* Translate record expression if it is an update *)
(* TODO: check for badrecord T97040801 *)
let record_id = Ident.create_fresh Ident.knormal in
let record_block =
match record with
| Some expr ->
let result = Present (Exp.Var record_id) in
[translate_expression {env with result} expr]
| None ->
[]
in
(* Translate each field: the value can come from 5 different sources *)
let translate_one_field ((one_field_name, one_id) : string * Ident.t) =
(* (1) Check if field is explicitly set *)
match String.Map.find updates_map one_field_name with
| Some expr ->
let result = Present (Exp.Var one_id) in
translate_expression {env with result} expr
| None -> (
(* (2) Check if field is set using 'everything else' *)
match String.Map.find updates_map "_" with
| Some expr ->
let result = Present (Exp.Var one_id) in
translate_expression {env with result} expr
| None -> (
let field_info = String.Map.find_exn record_info.field_info one_field_name in
(* (3) Check if we have to copy over from record that is being updated *)
match record with
| Some _ ->
let field_load =
load_field one_id
(ErlangTypeName.tuple_elem field_info.index)
(Var record_id) tuple_typ
in
Block.make_instruction env [field_load]
| None -> (
(* (4) Check if there is an initializer *)
match field_info.initializer_ with
| Some expr ->
let result = Present (Exp.Var one_id) in
translate_expression {env with result} expr
| None ->
(* (5) Finally, it's undefined *)
Block.make_load env one_id (translate_atom_literal "undefined") any ) ) )
in
let field_names = record_info.field_names in
let field_ids =
List.map ~f:(function _ -> Ident.create_fresh Ident.knormal) field_names
in
let field_blocks = List.map ~f:translate_one_field (List.zip_exn field_names field_ids) in
let field_ids_and_types = List.map ~f:(fun id -> (Exp.Var id, any)) field_ids in
let args_and_types = (translate_atom_literal name, any) :: field_ids_and_types in
let fun_exp = Exp.Const (Cfun BuiltinDecl.__erlang_make_tuple) in
let call_instruction =
Sil.Call ((ret_var, any), fun_exp, args_and_types, env.location, CallFlags.default)
in
let call_block = Block.make_instruction env [call_instruction] in
Block.all env (record_block @ field_blocks @ [call_block]) )
| Tuple exprs -> | Tuple exprs ->
let exprs_with_ids = List.map ~f:(fun e -> (e, Ident.create_fresh Ident.knormal)) exprs in let exprs_with_ids = List.map ~f:(fun e -> (e, Ident.create_fresh Ident.knormal)) exprs in
let expr_blocks = let expr_blocks =

@ -58,6 +58,25 @@ codetoanalyze/erlang/features/src/logic.erl, test_orelse00_Bad/0, 1, NO_TRUE_BRA
codetoanalyze/erlang/features/src/logic.erl, test_unot_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here] codetoanalyze/erlang/features/src/logic.erl, test_unot_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here]
codetoanalyze/erlang/features/src/logic.erl, test_xor00_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here] codetoanalyze/erlang/features/src/logic.erl, test_xor00_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here]
codetoanalyze/erlang/features/src/logic.erl, test_xor11_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here] codetoanalyze/erlang/features/src/logic.erl, test_xor11_Bad/0, 1, NO_TRUE_BRANCH_IN_IF, no_bucket, ERROR, [no true branch in if expression here]
codetoanalyze/erlang/features/src/records.erl, test_field2_Bad/0, -38, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field3_Bad/0, -50, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field4_Bad/0, -62, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_all_other1_Bad/0, -88, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_all_other2_Bad/0, -100, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_all_other3_Bad/0, -112, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_rearranged_Bad/0, -75, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_update1_Bad/0, -125, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_update2_Bad/0, -139, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_field_update3_Bad/0, -153, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_index2_Bad/0, -7, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_index3_Bad/0, -17, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_index4_Bad/0, -27, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_initializer1_Bad/0, -168, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_initializer2_Bad/0, -180, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_initializer_explicit_override_Bad/0, -192, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_initializer_update_override_Bad/0, -205, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, test_undefined_Bad/0, -218, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `warn/1`,no matching function clause here]
codetoanalyze/erlang/features/src/records.erl, warn/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/features/src/short_circuit.erl, accepts_one/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/features/src/short_circuit.erl, accepts_one/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/features/src/short_circuit.erl, test_and_Bad/0, -7, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_one/1`,no matching function clause here] codetoanalyze/erlang/features/src/short_circuit.erl, test_and_Bad/0, -7, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_one/1`,no matching function clause here]
codetoanalyze/erlang/features/src/short_circuit.erl, test_andalso_Bad/0, -15, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_one/1`,no matching function clause here] codetoanalyze/erlang/features/src/short_circuit.erl, test_andalso_Bad/0, -15, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_one/1`,no matching function clause here]

@ -0,0 +1,272 @@
% Copyright (c) Facebook, Inc. and its affiliates.
%
% This source code is licensed under the MIT license found in the
% LICENSE file in the root directory of this source tree.
-module(records).
-record(person, {name, phone, address}).
-export([
test_index2_Ok/0,
test_index2_Bad/0,
test_index3_Ok/0,
test_index3_Bad/0,
test_index4_Ok/0,
test_index4_Bad/0,
test_field2_Ok/0,
test_field2_Bad/0,
test_field3_Ok/0,
test_field3_Bad/0,
test_field4_Ok/0,
test_field4_Bad/0,
test_field_rearranged_Ok/0,
test_field_rearranged_Bad/0,
test_field_all_other1_Ok/0,
test_field_all_other1_Bad/0,
test_field_all_other2_Ok/0,
test_field_all_other2_Bad/0,
test_field_all_other3_Ok/0,
test_field_all_other3_Bad/0,
test_field_update1_Ok/0,
test_field_update1_Bad/0,
test_field_update2_Ok/0,
test_field_update2_Bad/0,
test_field_update3_Ok/0,
test_field_update3_Bad/0,
test_initializer1_Ok/0,
test_initializer1_Bad/0,
test_initializer2_Ok/0,
test_initializer2_Bad/0,
test_initializer_explicit_override_Ok/0,
test_initializer_explicit_override_Bad/0,
test_initializer_update_override_Ok/0,
test_initializer_update_override_Bad/0,
test_undefined_Ok/0,
test_undefined_Bad/0
]).
% Call this method with warn(1) to trigger a warning to expect
warn(0) -> ok.
test_index2_Ok() ->
case #person.name of
2 -> ok
end.
test_index2_Bad() ->
case #person.name of
2 -> warn(1)
end.
test_index3_Ok() ->
case #person.phone of
3 -> ok
end.
test_index3_Bad() ->
case #person.phone of
3 -> warn(1)
end.
test_index4_Ok() ->
case #person.address of
4 -> ok
end.
test_index4_Bad() ->
case #person.address of
4 -> warn(1)
end.
test_field2_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.name of
123 -> ok
end.
test_field2_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.name of
123 -> warn(1)
end.
test_field3_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.phone of
45 -> ok
end.
test_field3_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.phone of
45 -> warn(1)
end.
test_field4_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.address of
6789 -> ok
end.
test_field4_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P#person.address of
6789 -> warn(1)
end.
test_field_rearranged_Ok() ->
% Fields are set in different order
P = #person{phone = 45, address = 6789, name = 123},
case P#person.name of
123 -> ok
end.
test_field_rearranged_Bad() ->
% Fields are set in different order
P = #person{phone = 45, address = 6789, name = 123},
case P#person.name of
123 -> warn(1)
end.
test_field_all_other1_Ok() ->
P = #person{phone = 45, _ = 123},
case P#person.name of
123 -> ok
end.
test_field_all_other1_Bad() ->
P = #person{phone = 45, _ = 123},
case P#person.name of
123 -> warn(1)
end.
test_field_all_other2_Ok() ->
P = #person{phone = 45, _ = 123},
case P#person.address of
123 -> ok
end.
test_field_all_other2_Bad() ->
P = #person{phone = 45, _ = 123},
case P#person.address of
123 -> warn(1)
end.
test_field_all_other3_Ok() ->
P = #person{phone = 45, _ = 123},
case P#person.phone of
45 -> ok
end.
test_field_all_other3_Bad() ->
P = #person{phone = 45, _ = 123},
case P#person.phone of
45 -> warn(1)
end.
test_field_update1_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.phone of
0 -> ok
end.
test_field_update1_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.phone of
0 -> warn(1)
end.
test_field_update2_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.name of
123 -> ok
end.
test_field_update2_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.name of
123 -> warn(1)
end.
test_field_update3_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.address of
6789 -> ok
end.
test_field_update3_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
Q = P#person{phone = 0},
case Q#person.address of
6789 -> warn(1)
end.
-record(rabbit, {name = 123, color = 45}).
test_initializer1_Ok() ->
R = #rabbit{},
case R#rabbit.name of
123 -> ok
end.
test_initializer1_Bad() ->
R = #rabbit{},
case R#rabbit.name of
123 -> warn(1)
end.
test_initializer2_Ok() ->
R = #rabbit{},
case R#rabbit.color of
45 -> ok
end.
test_initializer2_Bad() ->
R = #rabbit{},
case R#rabbit.color of
45 -> warn(1)
end.
test_initializer_explicit_override_Ok() ->
R = #rabbit{name = 6789},
case R#rabbit.name of
6789 -> ok
end.
test_initializer_explicit_override_Bad() ->
R = #rabbit{name = 6789},
case R#rabbit.name of
6789 -> warn(1)
end.
test_initializer_update_override_Ok() ->
R = #rabbit{name = 987, color = 65},
Q = R#rabbit{name = 4321},
case Q#rabbit.name of
4321 -> ok
end.
test_initializer_update_override_Bad() ->
R = #rabbit{name = 987, color = 65},
Q = R#rabbit{name = 4321},
case Q#rabbit.name of
4321 -> warn(1)
end.
test_undefined_Ok() ->
P = #person{},
case P#person.name of
undefined -> ok
end.
test_undefined_Bad() ->
P = #person{},
case P#person.name of
undefined -> warn(1)
end.

@ -28,6 +28,21 @@ codetoanalyze/erlang/nonmatch/src/match.erl, match_test_e_Bad/0, -15, NO_MATCHIN
codetoanalyze/erlang/nonmatch/src/match.erl, match_test_g_Bad/0, 7, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `only_accepts_one/1`,no matching function clause here] codetoanalyze/erlang/nonmatch/src/match.erl, match_test_g_Bad/0, 7, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `only_accepts_one/1`,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/match.erl, only_accepts_one/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/nonmatch/src/match.erl, only_accepts_one/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/match.erl, tail/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/nonmatch/src/match.erl, tail/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, accepts_four_using_person/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, accepts_rabbits/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, accepts_three_using_rabbit/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_index3_Bad/0, -10, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_three_using_rabbit/1`,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_index4_Bad/0, -11, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_four_using_person/1`,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_as_tuple2_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_as_tuple3_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_as_tuple4_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_as_tuple5_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_field2_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_field3_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_field4_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_field_multiple2_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_match_field_multiple3_Bad/0, 2, NO_MATCHING_CASE_CLAUSE, no_bucket, ERROR, [no matching case clause here]
codetoanalyze/erlang/nonmatch/src/records.erl, test_type_Bad/0, -5, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [calling context starts here,in call to `accepts_rabbits/1`,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/tuples.erl, accepts_empty/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/nonmatch/src/tuples.erl, accepts_empty/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/tuples.erl, accepts_tuple_of_two/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/nonmatch/src/tuples.erl, accepts_tuple_of_two/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]
codetoanalyze/erlang/nonmatch/src/tuples.erl, first_from_at_most_three/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here] codetoanalyze/erlang/nonmatch/src/tuples.erl, first_from_at_most_three/1, 0, NO_MATCHING_FUNCTION_CLAUSE, no_bucket, ERROR, [*** LATENT ***,no matching function clause here]

@ -0,0 +1,140 @@
% Copyright (c) Facebook, Inc. and its affiliates.
%
% This source code is licensed under the MIT license found in the
% LICENSE file in the root directory of this source tree.
-module(records).
-record(person, {name, phone, address}).
-record(rabbit, {name, color}).
-export([
test_type_Ok/0,
test_type_Bad/0,
test_index1_Ok/0,
test_index2_Ok/0,
test_index3_Bad/0,
test_index4_Bad/0,
test_match_field2_Ok/0,
test_match_field2_Bad/0,
test_match_field3_Ok/0,
test_match_field3_Bad/0,
test_match_field4_Ok/0,
test_match_field4_Bad/0,
test_match_field_multiple1_Ok/0,
test_match_field_multiple2_Bad/0,
test_match_field_multiple3_Bad/0,
test_match_as_tuple1_Ok/0,
test_match_as_tuple2_Bad/0,
test_match_as_tuple3_Bad/0,
test_match_as_tuple4_Bad/0,
test_match_as_tuple5_Bad/0
]).
accepts_rabbits(#rabbit{}) -> ok.
test_type_Ok() ->
accepts_rabbits(#rabbit{name = "bunny", color = "brown"}).
test_type_Bad() ->
accepts_rabbits(#person{name = "alice", phone = 123, address = "LON"}).
accepts_three_using_rabbit(#rabbit.color) -> ok.
accepts_four_using_person(#person.address) -> ok.
test_index1_Ok() ->
accepts_three_using_rabbit(3).
test_index2_Ok() ->
accepts_four_using_person(4).
test_index3_Bad() ->
accepts_three_using_rabbit(2).
test_index4_Bad() ->
accepts_four_using_person(5).
test_match_field2_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{name = 123} -> ok
end.
test_match_field2_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{name = 9999999} -> ok
end.
test_match_field3_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{phone = 45} -> ok
end.
test_match_field3_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{phone = 9999999} -> ok
end.
test_match_field4_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{address = 6789} -> ok
end.
test_match_field4_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{address = 9999999} -> ok
end.
test_match_field_multiple1_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{address = 6789, name = 123} -> ok
end.
test_match_field_multiple2_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{address = 999999, name = 123} -> ok
end.
test_match_field_multiple3_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
#person{address = 6789, name = 99999} -> ok
end.
test_match_as_tuple1_Ok() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
{person, 123, 45, 6789} -> ok
end.
test_match_as_tuple2_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
{rabbit, 123, 45, 6789} -> ok
end.
test_match_as_tuple3_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
{person, 999999, 45, 6789} -> ok
end.
test_match_as_tuple4_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
{person, 123, 999999, 6789} -> ok
end.
test_match_as_tuple5_Bad() ->
P = #person{name = 123, phone = 45, address = 6789},
case P of
{person, 123, 45, 999999} -> ok
end.
Loading…
Cancel
Save