Skip to content

aufishgrp/istype

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

istype Hex.pm

Library that provides parse transforms to enhance Erlang type checking and conversion.

Usage

  1. Add the package to your rebar.config

    {deps, [
        {istype, "0.1.1"}
    ]}.
    
  2. Add the parse transform to your rebar.config

    {erl_opts, [
        {parse_transform, istype_transform}
    ]}.
    
  3. Use the transforms as documented in their sections below.

istype

Transform that generates guard friendly type checking statements based on the typespec given. This removes the need for the user to implement and maintain code for type validation.

Value istype

-type timeout() :: integer() || infinity.

%% Before transform
istype(Value, timeout()).

%% After transform
is_integer(Value) orelse Value =:= infinity.

The expression generated to validate type is generated in an in order manner.

-type timeout0() :: integer() || infinity.
-type timeout1() :: infinity || integer().

%% Before transform
istype(Value0, timeout0()).

istype(Value1, timeout1()).

%% After transform
is_integer(Value0) orelse Value0 =:= infinity.

Value1 =:= infinity orelse is_integer(Value1).

Expression istype

-type timeout() :: integer() || infinity.

%% Before transform
istype(expression(), timeout()).

%% After transform
begin
    __IsType_X = expression(),
    is_integer(__IsType_X) orelse __IsType_X =:= infinity
end.

When an expression is provided to istype, it is evaluated prior to any checks being processed. This prevents side effects from accidentally processing multiple times.

-type timeout() :: integer() || infinity.

%% Before transform
istype(size(Value), timeout()).

%% After transform
is_integer(size(Value)) orelse size(Value) =:= infinity.

Exceptions are made for any BIF that would be allowed in a guard statement. This is to allow the check to remain an allowable guard.

Tuple istype

-type tuple0() :: {atom}.

%% Before transform
istype(Value, tuple0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 1 andalso element(1, Value) =:= atom.

When a type specifies a tuple a specific tuple format as a primitive the arity and field types are also checked.

%% Before transform
istype(Value, tuple()).

%% After transform
is_tuple(Value).

The tuple() type is treated as any tuple.

Record istype

-record(record0, {a :: integer(), b}).

-type record0() :: #record0{}.

%% Before transform
istype(Value, record0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 3 andalso is_integer(Value#record0.a).

As with tuples records are checked against arity and field types.

Nested types

-type timeout() :: integer() | infinity.
-type tuple0() :: {timeout()}.

%% Before transform
istype(Value, tuple0()).

%% After transform
is_tuple(Value) andalso size(Value) =:= 1 andalso (is_integer(element(1, Value)) orelse
                                                   element(1, Value) =:= infinity).

asserttype

Transform that asserts that the value is the specified type. Functionally the same as true = istype(Value, type()).

totype

Transform that enables conversion to custom types. Generates a call to totype:convert/3 with the nested type specs needed for type conversion.

Value totype

-type timeout() :: integer().

1 = totype("1", timeout()).
1 = totype(1.0, timeout()).
1 = totype(<<"1">>, timeout()).

Type conversion looks at the input given for the target type and determines how to convert.

-type type0() :: binary() | list().
-type type1() :: list() | binary().

<<"1">> = totype(1, type0()).
"1" = totype(1, type1()).

When converting to a type that is the union of multiple types the first valid conversion is returned.

Records totype

-record(record0, {a = 0 :: integer()}).
-type record0() :: #record0{}.

#record0{a = 1} = totype([{a, 1}], record0()).
#record0{a = 1} = totype([{"a", "1"}], record0()).
#record0{a = 1} = totype(#{<<"a">> => <<"1">>}], record0()).

Records can be generated from maps and proplists. If a map or list contains a key that is not present in the record that value is dropped silently.

-record(record0, {a = 0 :: integer()}).
-record(record1, {a = <<"">> :: binary()}).

-type record0() :: #record0{}.
-type record1() :: #record1{}.

#record0{a = 1} = totype(#record0{a = "1"}, record0()).
#record1{a = <<"1">>} = totype(#record0{a = "1"}, record1()).

Records can be converted to other records. As with lists and maps only the shared keys are copied.

-record(record0, {a = 0 :: integer()}).

#{a => 2} = totype(#record{a = 2}, map()).
[{a, 2}] = totype(#record{a = 2}, list()).

Records can also be converted into maps and proplists.