├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── rebar.config ├── src ├── sheriff.app.src └── sheriff.erl └── test ├── external_type.erl └── sheriff_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .sheriff_dialyzer.plt 2 | deps 3 | ebin 4 | logs 5 | test/*.beam 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Loïc Hoguin 2 | 3 | Based on the awesome original work and research 4 | by William Dang and Hamza Mahmood. 5 | 6 | Copyright (c) 2011, William Dang , 7 | Hamza Mahmood 8 | 9 | Permission to use, copy, modify, and/or distribute this software for any 10 | purpose with or without fee is hereby granted, provided that the above 11 | copyright notice and this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # See LICENSE for licensing information. 2 | 3 | DIALYZER = dialyzer 4 | REBAR = rebar 5 | 6 | all: app 7 | 8 | app: deps 9 | @$(REBAR) compile 10 | 11 | deps: 12 | @$(REBAR) get-deps 13 | 14 | clean: 15 | @$(REBAR) clean 16 | rm -f test/*.beam 17 | rm -f erl_crash.dump 18 | 19 | tests: clean app ct 20 | 21 | ct: 22 | @$(REBAR) ct skip_deps=true 23 | 24 | build-plt: 25 | @$(DIALYZER) --build_plt --output_plt .sheriff_dialyzer.plt \ 26 | --apps kernel stdlib syntax_tools deps/* 27 | 28 | dialyze: 29 | @ERL_LIBS=deps $(DIALYZER) --src src --plt .sheriff_dialyzer.plt --no_native \ 30 | -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs 31 | 32 | docs: 33 | @$(REBAR) doc 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sheriff 2 | ======= 3 | 4 | Sheriff is a parse transform that allows developers to check 5 | values against their type as defined through typespecs. 6 | 7 | Sheriff generates validation functions for all the types in 8 | the module being compiled and then replaces all the sheriff:check/2 9 | calls with calls to these validation functions. 10 | 11 | Sheriff should be used where Dialyzer cannot do anything: when 12 | receiving external data, for example when reading from a file, 13 | a socket or receiving a process message. 14 | 15 | Currently Sheriff should support all types excluding iolist/0 16 | and maybe_improper_list/2. The main limitation is that it can 17 | only work with modules compiled with the Sheriff parse transform. 18 | 19 | Also note that Sheriff does not work with opaque types. If you 20 | try to check an opaque type, the file won't compile. If you try 21 | to check a type which include an opaque type, a runtime error 22 | will be produced. 23 | 24 | Usage 25 | ----- 26 | 27 | To compile a module with the Sheriff parse transform, simply add 28 | the following line at the top of your module: 29 | 30 | ``` erlang 31 | -compile({parse_transform, sheriff}). 32 | ``` 33 | 34 | This compilation option can also be defined project-wide using 35 | rebar.config or the Erlang compiler. 36 | 37 | Type Check 38 | ---------- 39 | 40 | To check that a value matches a type, you need to first define 41 | a type, then call sheriff:check/2 with the value and the type 42 | as arguments. 43 | 44 | ``` erlang 45 | -type colors() :: blue | red | green | yellow. 46 | 47 | paint(Color, Object) -> 48 | case sheriff:check(Color, colors) of 49 | true -> 50 | do_paint(Color, Object); 51 | false -> 52 | {error, badarg} 53 | end. 54 | ``` 55 | 56 | Many times you will probably want to let it crash, though. 57 | 58 | ``` erlang 59 | -type colors() :: blue | red | green | yellow. 60 | 61 | paint(Color, Object) -> 62 | true = sheriff:check(Color, colors), 63 | do_paint(Color, Object). 64 | ``` 65 | 66 | You can check records. All the typed record values will be 67 | checked, along with making sure the value is a record of the 68 | expected type. To check for recordness, you must first define a 69 | type specifically for the record. 70 | 71 | ``` erlang 72 | -type paintable_object() :: #paintable_object{}. 73 | 74 | paint(Color, Object) -> 75 | true = sheriff:check(Color, colors), 76 | true = sheriff:check(Object, paintable_object), 77 | do_paint(Color, Object). 78 | ``` 79 | 80 | You can also check against a remote type. 81 | 82 | ``` erlang 83 | paint(Color, Object) -> 84 | true = sheriff:check(Color, {picasso_module, colors}), 85 | do_paint(Color, Object). 86 | ``` 87 | 88 | You can finally use the inline notation. You can specify any 89 | built-in, local or remote type in a string and pass it to 90 | sheriff:check/2. 91 | 92 | ``` erlang 93 | paint(Color, Object) -> 94 | true = sheriff:check(Color, "picasso_module:colors()"), 95 | do_paint(Color, Object). 96 | 97 | erase(Pixels, Object) -> 98 | true = sheriff:check(Pixels, "list({integer(), integer()})"), 99 | do_erase(Pixels, Object). 100 | ``` 101 | 102 | Note that when passing atoms or tuples for the type to check 103 | against, Sheriff does not currently accept built-in types as 104 | arguments, only local or remote types. Also note that all types 105 | must be of arity 0, as sheriff:check/2 can only accept type 106 | names as argument at this time. This is a limitation only on 107 | the function call, not on the type specifications. You can use 108 | the inline notation to overcome it. 109 | 110 | ``` erlang 111 | %% This type cannot be passed to sheriff:check/2. 112 | -type a(A, B) :: [{A, B}]. 113 | 114 | %% These types can be passed to sheriff:check/2. 115 | -type b() :: a(atom(), integer()). 116 | -type c() :: list(integer()). 117 | -type d() :: picasso_module:colors(). 118 | ``` 119 | 120 | Thanks 121 | ------ 122 | 123 | Sheriff is available through the initial work and research 124 | by the students William Dang and Hamza Mahmood. 125 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {cover_enabled, true}. 2 | {deps, [ 3 | {parse_trans, ".*", 4 | {git, "git://github.com/esl/parse_trans.git", "HEAD"}} 5 | ]}. 6 | {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. 7 | {erl_opts, [ 8 | %% bin_opt_info, 9 | %% warn_missing_spec, 10 | %% warnings_as_errors, 11 | warn_export_all 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/sheriff.app.src: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2012, Loïc Hoguin 2 | %% 3 | %% Based on the awesome original work and research 4 | %% by William Dang and Hamza Mahmood. 5 | %% 6 | %% Copyright (c) 2011, William Dang , 7 | %% Hamza Mahmood 8 | %% 9 | %% Permission to use, copy, modify, and/or distribute this software for any 10 | %% purpose with or without fee is hereby granted, provided that the above 11 | %% copyright notice and this permission notice appear in all copies. 12 | %% 13 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | 21 | {application, sheriff, [ 22 | {description, "Parse transform for type based validation."}, 23 | {vsn, "0.4.1"}, 24 | {modules, []}, 25 | {applications, [kernel, stdlib]} 26 | ]}. 27 | -------------------------------------------------------------------------------- /src/sheriff.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2012, Loïc Hoguin 2 | %% 3 | %% Based on the awesome original work and research 4 | %% by William Dang and Hamza Mahmood. 5 | %% 6 | %% Copyright (c) 2011, William Dang , 7 | %% Hamza Mahmood 8 | %% 9 | %% Permission to use, copy, modify, and/or distribute this software for any 10 | %% purpose with or without fee is hereby granted, provided that the above 11 | %% copyright notice and this permission notice appear in all copies. 12 | %% 13 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 14 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 15 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 16 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 17 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 18 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | 21 | -module(sheriff). 22 | -compile({parse_transform, parse_trans_codegen}). 23 | 24 | -export([check/2, parse_transform/2, build_type/3]). 25 | 26 | %% @doc Return whether a value matches an Erlang type. 27 | %% 28 | %% This function takes two arguments: the value, and the type it will 29 | %% be matched against. Currently the type must be given either as 30 | %% an atom Type (for a local type of arity 0) or as a {Module, Type} 31 | %% tuple (for an external type of arity 0). Built-in types or types 32 | %% of other arities aren't supported as an argument at this point. 33 | %% To work around this limitation you need to define your own base 34 | %% type that will be a super-type of other built-in or user types. 35 | %% 36 | %% This function is actually a placeholder and will never be called, 37 | %% as Sheriff will replace all the sheriff:check/2 calls by a call 38 | %% to the generated validation function for the given type. 39 | -spec check(any(), atom() | {atom(), atom()}) -> boolean(). 40 | check(<<"sheriff world">>, world) -> true; 41 | check(_, _) -> false. 42 | 43 | parse_transform(Forms, _Options) -> 44 | %% @todo We only need to go through the top-level stuff. 45 | {_, Types} = parse_trans:depth_first( 46 | fun retrieve_types/4, [], Forms, []), 47 | Module = parse_trans:get_module(Forms), 48 | Funcs = gen_check_funcs(Types, Module), 49 | Forms2 = insert_funcs(Forms, Funcs, Types), 50 | {Forms3, _} = parse_trans:depth_first( 51 | fun replace_calls/4, Module, Forms2, []), 52 | parse_trans:revert(Forms3). 53 | 54 | retrieve_types(attribute, Form, _, Acc) -> 55 | case erl_syntax_lib:analyze_attribute(Form) of 56 | {type, {type, Type}} -> 57 | {Form, [Type|Acc]}; 58 | _ -> 59 | {Form, Acc} 60 | end; 61 | retrieve_types(_, Form, _, Acc) -> 62 | {Form, Acc}. 63 | 64 | gen_check_funcs(Types, Module) -> 65 | gen_check_funcs(Types, Module, []). 66 | gen_check_funcs([], Module, Acc) -> 67 | Func = codegen:gen_function('sherif_$_type_$_generic_$', 68 | fun(Val, Type) when is_atom(Type) -> 69 | TypeFunc = list_to_atom("sheriff_$_type_$_" ++ atom_to_list(Type)), 70 | {'$var', Module}:TypeFunc(Val); 71 | (Val, Type) when is_list(Type) -> 72 | {ModulePart, TypePart} = try 73 | [ModulePart2, TypePart2] = string:tokens(Type, ":"), 74 | [TypePart3, ")"] = string:tokens(TypePart2, "("), 75 | {ModulePart2, TypePart3} 76 | catch error:{badmatch, _} -> 77 | error(badarg) 78 | end, 79 | ModuleAtom = list_to_atom(ModulePart), 80 | TypeAtom = list_to_atom(TypePart), 81 | 'sherif_$_type_$_generic_$'(Val, {ModuleAtom, TypeAtom}); 82 | (Val, {ModuleAtom, TypeAtom}) -> 83 | TypeFunc = list_to_atom("sheriff_$_type_$_" ++ atom_to_list(TypeAtom)), 84 | ModuleAtom:TypeFunc(Val) 85 | end), 86 | [Func | Acc]; 87 | gen_check_funcs([{{record, Name}, Tree, []}|Tail], Module, Acc) -> 88 | FuncName = record_to_func_name(Name), 89 | Value = {var, 0, 'Sheriff_check_value'}, 90 | [Exprs] = build_record_exprs(Tree, Module, Value), 91 | Func = codegen:gen_function(FuncName, fun(Sheriff_check_value) -> 92 | {'$form', Exprs} 93 | end), 94 | gen_check_funcs(Tail, Module, [Func|Acc]); 95 | %% Special cases for types aliasing any() or term() to avoid an 96 | %% unnecessary warning with the variable being unused. 97 | gen_check_funcs([{Name, {type, L, TypeName, []}, Args}|Tail], Module, Acc) 98 | when TypeName =:= any; TypeName =:= term -> 99 | FuncName = type_to_func_name(Name), 100 | FuncArity = 1 + length(Args), 101 | Value = {var, 0, '_Sheriff_check_value'}, 102 | Func = {function, 0, FuncName, FuncArity, [ 103 | {clause, 0, [Value|Args], [], [{atom, L, true}]} 104 | ]}, 105 | gen_check_funcs(Tail, Module, [Func|Acc]); 106 | gen_check_funcs([{Name, Tree, Args}|Tail], Module, Acc) -> 107 | FuncName = type_to_func_name(Name), 108 | FuncArity = 1 + length(Args), 109 | Value = {var, 0, 'Sheriff_check_value'}, 110 | Exprs = build_exprs([Tree], Module, Value), 111 | Func = {function, 0, FuncName, FuncArity, [ 112 | {clause, 0, [Value|Args], [], Exprs} 113 | ]}, 114 | gen_check_funcs(Tail, Module, [Func|Acc]). 115 | 116 | build_exprs(Types, Module, Value) -> 117 | build_exprs(Types, Module, Value, []). 118 | build_exprs([], _, _, Acc) -> 119 | lists:reverse(Acc); 120 | build_exprs([Type|Tail], Module, Value, Acc) -> 121 | Expr = build_type(Type, Module, Value), 122 | build_exprs(Tail, Module, Value, [Expr|Acc]). 123 | 124 | build_record_exprs(Fields, Module, Value) -> 125 | [build_intersection(build_record_fields(Fields, Module, Value, 2, []))]. 126 | 127 | build_record_fields([], _, _, _, Acc) -> 128 | lists:reverse(Acc); 129 | %% Ignore untyped record fields. 130 | build_record_fields([Field|Tail], Module, Value, Pos, Acc) 131 | when element(1, Field) =:= record_field -> 132 | build_record_fields(Tail, Module, Value, Pos + 1, Acc); 133 | build_record_fields([Field|Tail], Module, Value, Pos, Acc) -> 134 | Expr = build_record_field(Field, Module, Value, Pos), 135 | build_record_fields(Tail, Module, Value, Pos + 1, [Expr|Acc]). 136 | 137 | build_record_field({typed_record_field, _, Type}, Module, Value, Pos) -> 138 | [Elem] = codegen:exprs(fun() -> 139 | element({'$var', Pos}, {'$form', Value}) 140 | end), 141 | build_type(Type, Module, Elem). 142 | 143 | %% Extract type information from annotated types. 144 | build_type({ann_type, _, [{var, _, _}, Type]}, Module, Value) -> 145 | build_type(Type, Module, Value); 146 | build_type(Expr = {atom, _, _}, _, Value) -> 147 | build_identity(Expr, Value); 148 | build_type(Expr = {integer, _, _}, _, Value) -> 149 | build_identity(Expr, Value); 150 | build_type(Expr = {op, _, '-', {integer, _, _}}, _, Value) -> 151 | build_identity(Expr, Value); 152 | build_type({remote_type, _, [{atom, _, Module}, {atom, _, Type}, Args]}, 153 | _, Value) -> 154 | FuncName = type_to_func_name(Type), 155 | [Exprs] = codegen:exprs(fun() -> 156 | apply({'$var', Module}, {'$var', FuncName}, 157 | [{'$form', Value}] ++ {'$var', Args}) 158 | end), 159 | Exprs; 160 | build_type({type, L, any, []}, _, _) -> 161 | {atom, L, true}; 162 | build_type({type, L, arity, []}, Module, Value) -> 163 | build_type({type, L, range, [{integer, L, 0}, {integer, L, 255}]}, 164 | Module, Value); 165 | build_type({type, _, atom, []}, _, Value) -> 166 | [Exprs] = codegen:exprs(fun() -> 167 | is_atom({'$form', Value}) 168 | end), 169 | Exprs; 170 | build_type({type, L, binary, []}, Module, Value) -> 171 | build_type({type, L, binary, [{integer, L, 0}, {integer, L, 8}]}, 172 | Module, Value); 173 | %% This one is <<>> specifically. 174 | build_type({type, L, binary, [{integer, _, 0}, {integer, _, 0}]}, 175 | _, Value) -> 176 | build_identity({bin, L, []}, Value); 177 | build_type({type, _, binary, [{integer, _, Size}, {integer, _, 0}]}, 178 | _, Value) -> 179 | [Exprs] = codegen:exprs(fun() -> 180 | is_bitstring({'$form', Value}) andalso 181 | bit_size({'$form', Value}) =:= {'$var', Size} 182 | end), 183 | Exprs; 184 | build_type({type, _, binary, [{integer, _, MinSize}, {integer, _, Div}]}, 185 | _, Value) -> 186 | [Exprs] = codegen:exprs(fun() -> 187 | is_bitstring({'$form', Value}) andalso 188 | (bit_size({'$form', Value}) - {'$var', MinSize}) 189 | rem {'$var', Div} =:= 0 190 | end), 191 | Exprs; 192 | build_type({type, _, bitstring, []}, _, Value) -> 193 | [Exprs] = codegen:exprs(fun() -> 194 | is_bitstring({'$form', Value}) 195 | end), 196 | Exprs; 197 | build_type({type, _, boolean, []}, _, Value) -> 198 | [Exprs] = codegen:exprs(fun() -> 199 | is_boolean({'$form', Value}) 200 | end), 201 | Exprs; 202 | build_type({type, L, byte, []}, Module, Value) -> 203 | build_type({type, L, range, [{integer, L, 0}, {integer, L, 255}]}, 204 | Module, Value); 205 | build_type({type, L, char, []}, Module, Value) -> 206 | build_type({type, L, range, [{integer, L, 0}, {integer, L, 16#10ffff}]}, 207 | Module, Value); 208 | build_type({type, _, float, []}, _, Value) -> 209 | [Exprs] = codegen:exprs(fun() -> 210 | is_float({'$form', Value}) 211 | end), 212 | Exprs; 213 | %% We can only check the function arity when provided. 214 | build_type({type, _, 'fun', [{type, _, product, Product}, _]}, _, Value) -> 215 | Arity = length(Product), 216 | [Exprs] = codegen:exprs(fun() -> 217 | is_function({'$form', Value}, {'$var', Arity}) 218 | end), 219 | Exprs; 220 | build_type({type, _, 'fun', _}, _, Value) -> 221 | [Exprs] = codegen:exprs(fun() -> 222 | is_function({'$form', Value}) 223 | end), 224 | Exprs; 225 | build_type({type, _, integer, []}, _, Value) -> 226 | [Exprs] = codegen:exprs(fun() -> 227 | is_integer({'$form', Value}) 228 | end), 229 | Exprs; 230 | %% @todo {type, _, iolist, []} 231 | build_type({type, _, list, []}, _, Value) -> 232 | [Exprs] = codegen:exprs(fun() -> 233 | is_list({'$form', Value}) andalso 234 | ({'$form', Value} =:= [] orelse is_list(tl({'$form', Value}))) 235 | end), 236 | Exprs; 237 | build_type({type, L, list, Types}, Module, Value) -> 238 | LCValue = {var, L, 'L'}, 239 | InExprs = build_union(build_exprs(Types, Module, LCValue)), 240 | [Exprs] = codegen:exprs(fun() -> 241 | is_list({'$form', Value}) andalso 242 | ({'$form', Value} =:= [] orelse is_list(tl({'$form', Value}))) andalso 243 | true =/= lists:member(false, 244 | [{'$form', InExprs} || {'$form', LCValue} <- {'$form', Value}]) 245 | end), 246 | Exprs; 247 | build_type({type, _, maybe_improper_list, []}, _, Value) -> 248 | [Exprs] = codegen:exprs(fun() -> 249 | is_list({'$form', Value}) 250 | end), 251 | Exprs; 252 | %% @todo 253 | %build_type({type, L, maybe_improper_list, 254 | % [{integer, _, Type1}, {integer, _, Type2}]}, Module, Value) -> 255 | %% @todo Same as the two above with nonempty_maybe_improper_list. 256 | build_type({type, L, mfa, []}, Module, Value) -> 257 | build_tuple([{type, L, atom, []}, {type, L, atom, []}, 258 | {type, L, byte, []}], Module, Value); 259 | build_type({type, L, module, []}, Module, Value) -> 260 | build_type({type, L, atom, []}, Module, Value); 261 | build_type({type, _, neg_integer, []}, _, Value) -> 262 | [Exprs] = codegen:exprs(fun() -> 263 | is_integer({'$form', Value}) andalso {'$form', Value} < 0 264 | end), 265 | Exprs; 266 | build_type({type, _, nil, []}, _, Value) -> 267 | [Exprs] = codegen:exprs(fun() -> 268 | {'$form', Value} =:= [] 269 | end), 270 | Exprs; 271 | build_type({type, L, no_return, []}, Module, Value) -> 272 | build_type({type, L, none, []}, Module, Value); 273 | build_type({type, L, node, []}, Module, Value) -> 274 | build_type({type, L, atom, []}, Module, Value); 275 | build_type({type, _, nonempty_list, []}, _, Value) -> 276 | [Exprs] = codegen:exprs(fun() -> 277 | is_list({'$form', Value}) andalso 278 | {'$form', Value} =/= [] andalso 279 | is_list(tl({'$form', Value})) 280 | end), 281 | Exprs; 282 | build_type({type, L, nonempty_list, Types}, Module, Value) -> 283 | LCValue = {var, L, 'L'}, 284 | InExprs = build_union(build_exprs(Types, Module, LCValue)), 285 | [Exprs] = codegen:exprs(fun() -> 286 | is_list({'$form', Value}) andalso 287 | {'$form', Value} =/= [] andalso 288 | is_list(tl({'$form', Value})) andalso 289 | true =/= lists:member(false, 290 | [{'$form', InExprs} || {'$form', LCValue} <- {'$form', Value}]) 291 | end), 292 | Exprs; 293 | build_type({type, L, nonempty_string, []}, Module, Value) -> 294 | StrExpr = build_type({type, L, string, []}, Module, Value), 295 | [Exprs] = codegen:exprs(fun() -> 296 | {'$form', StrExpr} andalso {'$form', Value} =/= [] 297 | end), 298 | Exprs; 299 | build_type({type, _, non_neg_integer, []}, _, Value) -> 300 | [Exprs] = codegen:exprs(fun() -> 301 | is_integer({'$form', Value}) andalso 0 =< {'$form', Value} 302 | end), 303 | Exprs; 304 | build_type({type, L, none, []}, _, _) -> 305 | {atom, L, false}; 306 | build_type({type, _, number, []}, _, Value) -> 307 | [Exprs] = codegen:exprs(fun() -> 308 | is_integer({'$form', Value}) orelse is_float({'$form', Value}) 309 | end), 310 | Exprs; 311 | build_type({type, _, pid, []}, _, Value) -> 312 | [Exprs] = codegen:exprs(fun() -> 313 | is_pid({'$form', Value}) 314 | end), 315 | Exprs; 316 | build_type({type, _, port, []}, _, Value) -> 317 | [Exprs] = codegen:exprs(fun() -> 318 | is_port({'$form', Value}) 319 | end), 320 | Exprs; 321 | build_type({type, _, pos_integer, []}, _, Value) -> 322 | [Exprs] = codegen:exprs(fun() -> 323 | is_integer({'$form', Value}) andalso 0 < {'$form', Value} 324 | end), 325 | Exprs; 326 | build_type({type, _, range, [From, To]}, _, Value) -> 327 | [Exprs] = codegen:exprs(fun() -> 328 | {'$form', From} =< {'$form', Value} andalso 329 | {'$form', Value} =< {'$form', To} 330 | end), 331 | Exprs; 332 | build_type({type, _, record, [{atom, _, Record}]}, _, Value) -> 333 | FuncName = record_to_func_name(Record), 334 | [Exprs] = codegen:exprs(fun() -> 335 | is_record({'$form', Value}, {'$var', Record}) andalso 336 | {'$var', FuncName}({'$form', Value}) 337 | end), 338 | Exprs; 339 | build_type({type, _, reference, []}, _, Value) -> 340 | [Exprs] = codegen:exprs(fun() -> 341 | is_reference({'$form', Value}) 342 | end), 343 | Exprs; 344 | build_type({type, L, string, []}, Module, Value) -> 345 | build_type({type, L, list, [{type, L, char, []}]}, Module, Value); 346 | build_type({type, L, term, []}, Module, Value) -> 347 | build_type({type, L, any, []}, Module, Value); 348 | build_type({type, _, timeout, []}, _, Value) -> 349 | [Exprs] = codegen:exprs(fun() -> 350 | {'$form', Value} =:= infinity orelse 351 | is_integer({'$form', Value}) andalso 0 =< {'$form', Value} 352 | end), 353 | Exprs; 354 | build_type({type, _, tuple, any}, _, Value) -> 355 | [Exprs] = codegen:exprs(fun() -> 356 | is_tuple({'$form', Value}) 357 | end), 358 | Exprs; 359 | build_type({type, _, tuple, []}, _, Value) -> 360 | [Exprs] = codegen:exprs(fun() -> 361 | {'$form', Value} =:= {} 362 | end), 363 | Exprs; 364 | build_type({type, _, tuple, Types}, Module, Value) -> 365 | build_tuple(Types, Module, Value); 366 | build_type({type, _, union, Types}, Module, Value) -> 367 | build_union(build_exprs(Types, Module, Value)); 368 | build_type({type, _, Custom, Args}, Module, Value) -> 369 | FuncName = type_to_func_name(Custom), 370 | [Exprs] = codegen:exprs(fun() -> 371 | apply({'$var', Module}, {'$var', FuncName}, 372 | [{'$form', Value}] ++ {'$var', Args}) 373 | end), 374 | Exprs; 375 | build_type({var, L, '_'}, Module, Value) -> 376 | build_type({type, L, any, []}, Module, Value); 377 | %% For type parameters, we dynamically obtain the validation AST 378 | %% for the parameter, then eval it and return the result. 379 | build_type(Expr = {var, _, _}, Module, Value) -> 380 | [Exprs] = codegen:exprs(fun() -> 381 | %% Hide the variable Result in a fun to avoid conflicts 382 | %% when a type has more than one parameter. 383 | (fun() -> 384 | {value, Result, []} = erl_eval:exprs( 385 | [sheriff:build_type( 386 | {'$form', Expr}, 387 | {'$var', Module}, 388 | erl_parse:abstract({'$form', Value}))], 389 | []), 390 | Result 391 | end)() 392 | end), 393 | Exprs. 394 | 395 | build_identity(Expr, Value) -> 396 | [Exprs] = codegen:exprs(fun() -> 397 | {'$form', Expr} =:= {'$form', Value} 398 | end), 399 | Exprs. 400 | 401 | build_tuple(Types = [Type|Tail], Module, Value) -> 402 | Size = length(Types), 403 | First = build_tuple_element(Type, Module, Value, 1), 404 | Elems = build_tuple_elements(Tail, Module, Value, 2, First), 405 | [Exprs] = codegen:exprs(fun() -> 406 | is_tuple({'$form', Value}) andalso 407 | size({'$form', Value}) =:= {'$var', Size} andalso 408 | {'$form', Elems} 409 | end), 410 | Exprs. 411 | 412 | build_tuple_elements([], _, _, _, Expr) -> 413 | Expr; 414 | build_tuple_elements([Type|Tail], Module, Value, N, Expr2) -> 415 | Expr1 = build_tuple_element(Type, Module, Value, N), 416 | [Elems] = codegen:exprs(fun() -> 417 | {'$form', Expr1} andalso {'$form', Expr2} 418 | end), 419 | build_tuple_elements(Tail, Module, Value, N + 1, Elems). 420 | 421 | build_tuple_element(Type, Module, Value, N) -> 422 | [Elem] = codegen:exprs(fun() -> 423 | element({'$var', N}, {'$form', Value}) 424 | end), 425 | build_type(Type, Module, Elem). 426 | 427 | build_intersection([Expr|Tail]) -> 428 | build_intersection(Tail, Expr). 429 | build_intersection([], Expr) -> 430 | Expr; 431 | build_intersection([Expr1|Tail], Expr2) -> 432 | [Intersection] = codegen:exprs(fun() -> 433 | {'$form', Expr1} andalso {'$form', Expr2} 434 | end), 435 | build_intersection(Tail, Intersection). 436 | 437 | build_union([Expr|Tail]) -> 438 | build_union(Tail, Expr). 439 | build_union([], Expr) -> 440 | Expr; 441 | build_union([Expr1|Tail], Expr2) -> 442 | [Union] = codegen:exprs(fun() -> 443 | {'$form', Expr1} orelse {'$form', Expr2} 444 | end), 445 | build_union(Tail, Union). 446 | 447 | insert_funcs(Forms, Funcs, Types) -> 448 | Forms2 = parse_trans:do_insert_forms(below, Funcs, Forms, 449 | parse_trans:initial_context(Forms, [])), 450 | lists:foldl(fun({Type, _, Args}, FormsAcc) -> 451 | case Type of 452 | {record, _} -> 453 | FormsAcc; 454 | Type -> 455 | FuncName = type_to_func_name(Type), 456 | FuncArity = 1 + length(Args), 457 | parse_trans:export_function(FuncName, FuncArity, FormsAcc) 458 | end 459 | end, Forms2, Types). 460 | 461 | replace_calls(application, Form, _Ctx, ThisModule) -> 462 | case erl_syntax_lib:analyze_application(Form) of 463 | {sheriff, {check, 2}} -> 464 | Pos = erl_syntax:get_pos(Form), 465 | Args = erl_syntax:application_arguments(Form), 466 | Vars = [hd(Args)], 467 | [CheckVar, TypeVar] = parse_trans:revert(Args), 468 | Form2 = case TypeVar of 469 | {var, _, _} -> 470 | erl_syntax:application( 471 | erl_syntax:atom('sherif_$_type_$_generic_$'), 472 | Args); 473 | {string, _, String} -> 474 | {ok, Ts, _} = erl_scan:string( 475 | "-type sheriff_string_arg() :: " ++ String ++ "."), 476 | {ok, {attribute, _, type, {sheriff_string_arg, Type, []}}} 477 | = erl_parse:parse_form(Ts), 478 | build_type(Type, ThisModule, CheckVar); 479 | {tuple, _, [{atom, _, Module}, {atom, _, Type}]} -> 480 | FuncName = type_to_func_name(Type), 481 | erl_syntax:application(erl_syntax:atom(Module), 482 | erl_syntax:atom(FuncName), Vars); 483 | {atom, _, Type} -> 484 | FuncName = type_to_func_name(Type), 485 | erl_syntax:application(erl_syntax:atom(FuncName), Vars) 486 | end, 487 | Form3 = erl_syntax:set_pos(Form2, Pos), 488 | {Form3, ThisModule}; 489 | _ -> 490 | {Form, ThisModule} 491 | end; 492 | replace_calls(_, Form, _Ctx, ThisModule) -> 493 | {Form, ThisModule}. 494 | 495 | type_to_func_name(Type) when is_atom(Type) -> 496 | list_to_atom("sheriff_$_type_$_" ++ atom_to_list(Type)). 497 | 498 | record_to_func_name(Record) when is_atom(Record) -> 499 | list_to_atom("sheriff_$_record_$_" ++ atom_to_list(Record)). 500 | -------------------------------------------------------------------------------- /test/external_type.erl: -------------------------------------------------------------------------------- 1 | -module(external_type). 2 | -compile({parse_transform, sheriff}). 3 | 4 | -export_type([a/0]). 5 | -type a() :: atom() | list() | tuple(). 6 | 7 | -export_type([b/2]). 8 | -type b(A, B) :: atom() | {A, B}. 9 | 10 | -export_type([c/0]). 11 | -type c() :: any(). 12 | -------------------------------------------------------------------------------- /test/sheriff_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(sheriff_SUITE). 2 | -compile({parse_transform, sheriff}). 3 | -export([all/0, groups/0]). 4 | -export([ 5 | t_all/1, 6 | t_custom_a/1, t_custom_b/1, t_custom_c/1, t_custom_d/1, 7 | t_custom_e/1, t_custom_f/1, t_custom_g/1, t_custom_h/1, 8 | t_custom_i/1, t_custom_j/1, t_custom_k/1, t_custom_l/1, 9 | t_custom_m/1, t_custom_n/1, t_custom_o/1, t_custom_p/1, 10 | t_custom_q/1, t_custom_r/1, t_custom_s/1, t_custom_t/1, 11 | t_external_a/1, t_external_b/1, t_external_c/1, 12 | t_external_d/1, t_external_e/1, t_external_f/1, t_external_g/1, 13 | t_record_a/1, 14 | t_string_a/1,t_string_b/1]). 15 | 16 | -include_lib("common_test/include/ct.hrl"). 17 | 18 | %% Export the types just to get rid of warnings. 19 | -export_type([ 20 | my_record/0, 21 | my_type/0, all/0, 22 | a/0, b/0, c/0, d/0, e/0, f/0, g/0, h/0, 23 | i/0, j/0, k/0, l/0, m/0, n/0, o/0, p/0, 24 | q/0, r/0, s/0, t/0, 25 | external_b/0, external_c/0, external_d/0, 26 | external_e/0]). 27 | 28 | -record(my_record, { 29 | id :: integer(), 30 | value = 2 :: integer(), 31 | bad_default = undefined :: integer(), 32 | no_type = undefined, 33 | no_type_no_default 34 | }). 35 | -type my_record() :: #my_record{}. 36 | 37 | %% These two types are just used to check we can compile any type check. 38 | -type my_type() :: 1 | 3 | 5 | 7 | 9. 39 | -type all() :: 40 | %% Literal values and notations. 41 | _ | %% any() 42 | an_atom | 43 | <<>> | 44 | << _:3 >> | 45 | << _:_*5 >> | 46 | << _:3, _:_*5 >> | 47 | -42 | 48 | 42 | 49 | [] | 50 | [integer()] | 51 | [byte(), ...] | 52 | -10..-5 | 53 | 5..100 | 54 | {} | 55 | {atom(), integer(), float()} | 56 | 57 | %% Built-in types. 58 | any() | 59 | arity() | %% 0..255 60 | atom() | 61 | binary() | %% << _:0, _:_*8 >> 62 | binary(3, 5) | %% << _:3, _:_*5 >> 63 | bitstring() | 64 | boolean() | %% true | false 65 | byte() | %% 0..255 66 | char() | %% 0..16#10ffff 67 | float() | 68 | fun() | 69 | fun(() -> integer()) | 70 | fun((...) -> integer()) | 71 | fun((atom(), tuple()) -> integer()) | 72 | integer() | 73 | % iodata() | %% @todo 74 | % iolist() | %% @todo 75 | list() | %% [any()] 76 | list(integer()) | %% [integer()] 77 | maybe_improper_list() | 78 | % maybe_improper_list(atom(), integer()) | %% @todo 79 | mfa() | %% {atom(), atom(), arity()} 80 | module() | %% atom() only, because tuple() is deprecated 81 | neg_integer() | %% ..-1 82 | nil() | %% [] 83 | no_return() | %% none() 84 | node() | %% atom() 85 | none() | 86 | nonempty_list() | %% [any(), ...] 87 | nonempty_list(byte()) | %% [byte(), ...] 88 | % nonempty_improper_list(atom(), integer()) | %% @todo 89 | % nonempty_maybe_improper_list() | %% @todo 90 | % nonempty_maybe_improper_list(atom(), integer()) | %% @todo 91 | nonempty_string() | %% [byte(), ...] 92 | non_neg_integer() | %% 0.. 93 | number() | 94 | pid() | 95 | port() | 96 | pos_integer() | %% 1.. 97 | range(5, 100) | %% 5..100 98 | reference() | 99 | string() | %% [byte()] 100 | term() | %% any() 101 | timeout() | %% 0.. | infinity 102 | tuple() | 103 | tuple(atom()) | %% {atom()} 104 | tuple(atom(), integer()) | %% {atom(), integer()} 105 | tuple(atom(), integer(), float()) | %% {atom(), integer(), float()} 106 | 107 | %% Variables. 108 | %% A | B | C 109 | 110 | %% Annotated types. 111 | {Key :: atom(), Value :: any()} | 112 | 113 | %% User-defined records and types. 114 | #my_record{} | 115 | my_type(). 116 | 117 | all() -> 118 | [{group, check}]. 119 | 120 | groups() -> 121 | [{check, [], [ 122 | t_all, 123 | t_custom_a, t_custom_b, t_custom_c, t_custom_d, 124 | t_custom_e, t_custom_f, t_custom_g, t_custom_h, 125 | t_custom_i, t_custom_j, t_custom_k, t_custom_l, 126 | t_custom_m, t_custom_n, t_custom_o, t_custom_p, 127 | t_custom_q, t_custom_r, t_custom_s, t_custom_t, 128 | t_external_a, t_external_b, t_external_c, 129 | t_external_d, t_external_e, t_external_f, t_external_g, 130 | t_record_a, 131 | t_string_a,t_string_b 132 | ]}]. 133 | 134 | t_all(_) -> 135 | true = sheriff:check([{"a", b}], all). 136 | 137 | -type a() :: atom() | list() | tuple(). 138 | t_custom_a(_) -> 139 | true = sheriff:check([2, {5}, 2.2, at, "p"], a), 140 | true = sheriff:check({[2, {5}, 2.2, at, "p"]}, a), 141 | true = sheriff:check(at, a), 142 | false = sheriff:check(2.3, a). 143 | 144 | -type b() :: [1 | a()]. 145 | t_custom_b(_) -> 146 | true = sheriff:check([1], b), 147 | true = sheriff:check([{[2, {5}, 2.2, at, "p"]}], b), 148 | false = sheriff:check(2.3, b). 149 | 150 | -type c() :: [{a()} | list()]. 151 | t_custom_c(_) -> 152 | true = sheriff:check([{[a]}], c), 153 | true = sheriff:check([[azerty], [qsd]], c), 154 | false = sheriff:check(1.2, c). 155 | 156 | -type d() :: [integer() | {d()}]. 157 | t_custom_d(_) -> 158 | true = sheriff:check([5, 5, 2, -7, 10000000], d), 159 | true = sheriff:check([{[5, 2, 4]}], d), 160 | false = sheriff:check([atom, atom2], d). 161 | 162 | -type e() :: -5..5 | {tuple(tuple())}. 163 | t_custom_e(_) -> 164 | true = sheriff:check(-3, e), 165 | true = sheriff:check({{{a, 1, "a"}}}, e), 166 | false = sheriff:check({1}, e). 167 | 168 | -type f() :: [f() | atom()]. 169 | t_custom_f(_) -> 170 | true = sheriff:check([atom, atom, [atom]], f), 171 | false = sheriff:check([atom, atom, [atom, [[[[[[[1]]]]]]]]], f), 172 | false = sheriff:check([[2.3], atom], f). 173 | 174 | -type g() :: {any()}. 175 | t_custom_g(_) -> 176 | true = sheriff:check({atom}, g), 177 | false = sheriff:check({1, "a"}, g). 178 | 179 | -type h() :: binary() | pos_integer() | string(). 180 | t_custom_h(_) -> 181 | true = sheriff:check(<<1>>, h), 182 | true = sheriff:check(1, h), 183 | true = sheriff:check("popopo", h), 184 | false = sheriff:check(atom, h). 185 | 186 | -type i() :: [byte(), ...]. 187 | t_custom_i(_) -> 188 | true = sheriff:check([0, 45, 255, 54, 2], i), 189 | true = sheriff:check("azerty", i), 190 | false = sheriff:check("", i), 191 | false = sheriff:check([0, 45, 255, 54, 2000], i). 192 | 193 | -type j() :: [char()] | [neg_integer()]. 194 | t_custom_j(_) -> 195 | true = sheriff:check("azerty" ++ [2000], j), 196 | true = sheriff:check([-8000, -1], j), 197 | false = sheriff:check(["t", -5], j). 198 | 199 | -type list_of(A) :: list(A). 200 | -type k() :: list_of(integer()). 201 | t_custom_k(_) -> 202 | true = sheriff:check([1, 2, 3], k), 203 | true = sheriff:check([42, 80085, 999999999999999], k), 204 | true = sheriff:check("it works!", k), 205 | false = sheriff:check(atom, k), 206 | false = sheriff:check([atom], k), 207 | false = sheriff:check(["it shouldn't"], k). 208 | 209 | -type l_base(A) :: [{A, boolean()}|c()]. 210 | -type l() :: l_base(integer()). 211 | t_custom_l(_) -> 212 | true = sheriff:check([[{atom}]], l), 213 | true = sheriff:check([[{atom}], [[], [a, "b"]], {42, false}], l), 214 | false = sheriff:check(42, l). 215 | 216 | -type m() :: pid() | reference() | none. 217 | t_custom_m(_) -> 218 | true = sheriff:check(self(), m), 219 | true = sheriff:check(make_ref(), m), 220 | true = sheriff:check(none, m), 221 | false = sheriff:check(other, m), 222 | false = sheriff:check({self(), none}, m). 223 | 224 | -type n() :: fun((integer(), atom()) -> tuple()). 225 | t_custom_n(_) -> 226 | true = sheriff:check(fun(A, B) -> {A, B} end, n), 227 | false = sheriff:check(fun() -> ok end, n), 228 | false = sheriff:check(atom, n). 229 | 230 | -type o() :: list_of(a()). 231 | t_custom_o(_) -> 232 | true = sheriff:check([atom, [list, 42], {tuple}], o), 233 | true = sheriff:check([a, b, c, d, e], o), 234 | false = sheriff:check(atom, o), 235 | false = sheriff:check([atom, [list], {tuple}, 42], o). 236 | 237 | -type p() :: <<>>. 238 | t_custom_p(_) -> 239 | true = sheriff:check(<<>>, p), 240 | false = sheriff:check(<< 0:2 >>, p), 241 | false = sheriff:check(<< 0:8 >>, p), 242 | false = sheriff:check(atom, p). 243 | 244 | -type q() :: << _:4, _:_*8 >>. 245 | t_custom_q(_) -> 246 | true = sheriff:check(<< 1:4 >>, q), 247 | true = sheriff:check(<< 1:4, 0:8, 9:8 >>, q), 248 | false = sheriff:check(<< 1:4, 0:7 >>, q), 249 | false = sheriff:check(<< 1:3, 0:8 >>, q), 250 | false = sheriff:check(atom, q). 251 | 252 | -type r() :: list(integer()). 253 | t_custom_r(_) -> 254 | true = sheriff:check([2, 3], r), 255 | true = sheriff:check([2|[3]], r), 256 | true = sheriff:check([2, 3|[]], r), 257 | false = sheriff:check([2|3], r). 258 | 259 | -type s() :: maybe_improper_list(). 260 | t_custom_s(_) -> 261 | true = sheriff:check([2, 3], s), 262 | true = sheriff:check([2|[3]], s), 263 | true = sheriff:check([2, 3|[]], s), 264 | true = sheriff:check([2|3], s), 265 | false = sheriff:check(atom, s). 266 | 267 | -type t() :: {Key :: atom(), Value :: any()}. 268 | t_custom_t(_) -> 269 | true = sheriff:check({key, value}, t), 270 | true = sheriff:check({key, [[[<<"list">>]]]}, t), 271 | false = sheriff:check({key}, t), 272 | false = sheriff:check({key, value, more}, t), 273 | false = sheriff:check({<<"key">>, <<"value">>}, t), 274 | false = sheriff:check(123, t). 275 | 276 | %% Note that external_type is the name of the module, not a keyword. 277 | t_external_a(_) -> 278 | true = sheriff:check(test, {external_type, a}), 279 | false = sheriff:check(1.2, {external_type, a}). 280 | 281 | -type external_b() :: external_type:b(atom(), integer()). 282 | t_external_b(_) -> 283 | true = sheriff:check(test, external_b), 284 | true = sheriff:check({test, 42}, external_b), 285 | false = sheriff:check({42, test}, external_b). 286 | 287 | -type external_c_base(A, B) :: [{A, B}]. 288 | -type external_c() :: external_c_base(external_type:a(), boolean()). 289 | t_external_c(_) -> 290 | true = sheriff:check([{test, true}], external_c), 291 | false = sheriff:check(test, external_c), 292 | false = sheriff:check([{4.2, true}], external_c). 293 | 294 | -type external_d() :: external_type:b(external_type:a(), integer()). 295 | t_external_d(_) -> 296 | true = sheriff:check({[list], 42}, external_d), 297 | true = sheriff:check({atom, 42}, external_d), 298 | true = sheriff:check(atom, external_d), 299 | false = sheriff:check({4.2, 42}, external_d). 300 | 301 | -type external_e() :: external_type:b(a(), integer()). 302 | t_external_e(_) -> 303 | Type = external_e, 304 | true = sheriff:check({[list], 42}, external_e), 305 | true = sheriff:check({[list], 42}, Type), 306 | true = sheriff:check({atom, 42}, external_e), 307 | true = sheriff:check(atom, external_e), 308 | false = sheriff:check({4.2, 42}, external_e), 309 | false = sheriff:check({4.2, 42}, Type). 310 | 311 | t_external_f(_) -> 312 | Type = "external_type:c()", 313 | true = sheriff:check(abc, "external_type:c()"), 314 | true = sheriff:check(abc, Type), 315 | true = sheriff:check(123, "external_type:c()"), 316 | true = sheriff:check({[]}, "external_type:c()"). 317 | 318 | t_external_g(_) -> 319 | Type = {external_type, c}, 320 | true = sheriff:check(abc, {external_type, c}), 321 | true = sheriff:check(abc, Type), 322 | true = sheriff:check(123, {external_type, c}), 323 | true = sheriff:check({[]}, {external_type, c}). 324 | 325 | t_record_a(_) -> 326 | Type = my_record, 327 | true = sheriff:check(#my_record{id=123, bad_default=456, 328 | no_type=a, no_type_no_default={1, 2, 3}}, my_record), 329 | true = sheriff:check(#my_record{id=123, bad_default=456, 330 | no_type=a, no_type_no_default={1, 2, 3}}, Type), 331 | false = sheriff:check({wrong_rec, 123, 2, 456, a, {1, 2, 3}}, my_record), 332 | false = sheriff:check(#my_record{}, my_record), 333 | false = sheriff:check(#my_record{}, Type), 334 | false = sheriff:check(#my_record{id=123}, my_record), 335 | false = sheriff:check({}, my_record), 336 | false = sheriff:check(123, my_record). 337 | 338 | t_string_a(_) -> 339 | true = sheriff:check([a, b, c], "list(atom())"), 340 | true = sheriff:check(test, "external_type:a()"), 341 | true = sheriff:check([1, 2, 3], "list_of(integer())"), 342 | false = sheriff:check({a, b}, "{atom(), integer()}"). 343 | 344 | t_string_b(_) -> 345 | Type = "list(atom())", 346 | it_fails = try 347 | sheriff:check([a, b, c], Type) 348 | catch error:badarg -> 349 | it_fails 350 | end. 351 | 352 | 353 | --------------------------------------------------------------------------------