Skip to content

Commit 6eadfe6

Browse files
author
CI
committed
Sync to GitHub
1 parent a58014c commit 6eadfe6

3 files changed

Lines changed: 995 additions & 51 deletions

File tree

lib/bacnet/beam_types.ex

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -81,31 +81,17 @@ defmodule BACnet.BeamTypes do
8181

8282
def check_type(:any, _value), do: true
8383

84-
def check_type(:boolean, value) when is_boolean(value), do: true
85-
def check_type(:boolean, _value), do: false
84+
def check_type(:boolean, value), do: is_boolean(value)
8685

87-
def check_type(:string, value) when is_binary(value), do: true
88-
def check_type(:string, _value), do: false
86+
def check_type(:string, value), do: is_binary(value) and String.valid?(value)
87+
def check_type(:octet_string, value), do: is_binary(value)
8988

90-
def check_type(:octet_string, value) when is_binary(value), do: true
91-
def check_type(:octet_string, _value), do: false
89+
def check_type(:signed_integer, value), do: is_integer(value)
90+
def check_type(:unsigned_integer, value), do: is_integer(value) and value >= 0
9291

93-
def check_type(:signed_integer, value) when is_integer(value), do: true
94-
def check_type(:signed_integer, _value), do: false
92+
def check_type(:real, value), do: is_float(value) or value in [:NaN, :inf, :infn]
9593

96-
def check_type(:unsigned_integer, value)
97-
when is_integer(value) and value >= 0,
98-
do: true
99-
100-
def check_type(:unsigned_integer, _value), do: false
101-
102-
def check_type(:real, value) when is_float(value), do: true
103-
def check_type(:real, value) when value in [:NaN, :inf, :infn], do: true
104-
def check_type(:real, _value), do: false
105-
106-
def check_type(:double, value) when is_float(value), do: true
107-
def check_type(:double, value) when value in [:NaN, :inf, :infn], do: true
108-
def check_type(:double, _value), do: false
94+
def check_type(:double, value), do: is_float(value) or value in [:NaN, :inf, :infn]
10995

11096
def check_type(:bitstring, value) when is_tuple(value) do
11197
value
@@ -142,32 +128,28 @@ defmodule BACnet.BeamTypes do
142128
when is_integer(fixed_size) and fixed_size >= 1,
143129
do: false
144130

145-
def check_type({:constant, type}, value) when is_atom(value),
146-
do: Constants.has_by_name(type, value)
131+
def check_type({:constant, type}, value) when is_atom(type),
132+
do: is_atom(value) and Constants.has_by_name(type, value)
147133

148134
def check_type({:in_list, values}, value) when is_list(values) do
149135
value in values
150136
end
151137

152-
def check_type({:in_range, low, high}, value) do
138+
def check_type({:in_range, low, high}, value) when is_integer(low) and is_integer(high) do
153139
is_integer(value) and value >= low and value <= high
154140
end
155141

156142
def check_type({:list, _type}, []), do: true
157143

158144
def check_type({:list, type}, value) when is_list(value) do
159-
if Enumerable.impl_for(value) do
160-
Enum.all?(value, &check_type(type, &1))
161-
else
162-
false
163-
end
145+
Enum.all?(value, &check_type(type, &1))
164146
end
165147

166148
def check_type({:list, _type}, _value), do: false
167149

168150
def check_type({:literal, eq}, value), do: value === eq
169151

170-
def check_type({:tuple, subtypes}, value) when is_tuple(value) do
152+
def check_type({:tuple, subtypes}, value) when is_list(subtypes) and is_tuple(value) do
171153
if length(subtypes) == tuple_size(value) do
172154
subtypes
173155
|> Enum.with_index()
@@ -179,7 +161,7 @@ defmodule BACnet.BeamTypes do
179161
end
180162
end
181163

182-
def check_type({:tuple, _subtypes}, _value), do: false
164+
def check_type({:tuple, subtypes}, _value) when is_list(subtypes), do: false
183165

184166
def check_type({:struct, type}, value) when is_struct(value, type) do
185167
if function_exported?(type, :valid?, 1) do
@@ -196,27 +178,37 @@ defmodule BACnet.BeamTypes do
196178
end
197179

198180
def check_type({:with_validator, type, validator}, value) when is_function(validator, 1) do
199-
if check_type(type, value) do
200-
validator.(value)
201-
else
202-
false
203-
end
181+
check_type_validator(type, validator, value)
204182
end
205183

206184
def check_type({:with_validator, type, validator_ast}, value) do
207185
{validator, _bind} = Code.eval_quoted(validator_ast, [], __ENV__)
208-
check_type({:with_validator, type, validator}, value)
186+
check_type_validator(type, validator, value)
187+
end
188+
189+
def check_type(type, _value) do
190+
raise ArgumentError, "Unknown type: #{inspect(type)}"
209191
end
210192

211-
def check_type(type, _value), do: raise("Unknown type: #{inspect(type)}")
193+
# This clause is needed to prevent an infinite loop
194+
defp check_type_validator(type, validator, value) do
195+
if is_function(validator, 1) do
196+
check_type(type, value) and validator.(value)
197+
else
198+
raise ArgumentError, "Only one arity functions are allowed for :with_validator"
199+
end
200+
end
212201

213202
@doc """
214203
Generates `valid?/1` clause body based on the given module's `:t` typespec,
215204
it must reference a struct.
205+
206+
The variable used for the validation is called `t`.
207+
Keys that start with an underline are ignored.
216208
"""
217209
@spec generate_valid_clause(module(), Macro.Env.t()) :: Macro.t()
218210
def generate_valid_clause(module, env) when is_atom(module) do
219-
validation = resolve_struct_type(module, :t, env)
211+
validation = resolve_struct_type(module, :t, env, ignore_underlined_keys: true)
220212

221213
if map_size(validation) == 0 do
222214
quote do
@@ -261,7 +253,7 @@ defmodule BACnet.BeamTypes do
261253
Tuple types such as `{binary(), integer()}` will be resolved to `{:tuple, [:octet_string, :integer]}`.
262254
263255
```elixir
264-
iex(1)> resolve_struct_type(BACnet.Protocol.EventParameters.ChangeOfBitstring, :t, __ENV__)
256+
iex> resolve_struct_type(BACnet.Protocol.EventParameters.ChangeOfBitstring, :t, __ENV__)
265257
%{
266258
alarm_values: {:list, :bitstring},
267259
bitmask: :bitstring,
@@ -292,10 +284,11 @@ defmodule BACnet.BeamTypes do
292284
{:type, _line, :map, fields} ->
293285
fields
294286
|> Map.new(fn
295-
{:type, _line, :map_field_exact, [{:atom, 0, :__struct__}, {:atom, 0, ^module}]} ->
287+
{:type, _line, :map_field_exact,
288+
[{:atom, _num, :__struct__}, {:atom, _other, ^module}]} ->
296289
{:__drop__, nil}
297290

298-
{:type, _line, :map_field_exact, [{:atom, 0, key}, spec]} ->
291+
{:type, _line, :map_field_exact, [{:atom, _num, key}, spec]} ->
299292
if ignore_underlined_keys and String.starts_with?(Atom.to_string(key), "_") do
300293
{:__drop__, nil}
301294
else
@@ -308,8 +301,10 @@ defmodule BACnet.BeamTypes do
308301
|> Map.delete(:__drop__)
309302

310303
_term ->
304+
str_mod = String.replace("#{module}", "Elixir.", "")
305+
311306
raise CompileError,
312-
description: "Type #{module}.#{type} does not export the type as struct",
307+
description: "Type #{str_mod}.#{type} does not export the type as struct",
313308
file: env.file,
314309
line: env.line
315310
end
@@ -331,6 +326,11 @@ defmodule BACnet.BeamTypes do
331326
nil
332327
end
333328

329+
# Map true and false to boolean()
330+
defp field_typespec_to_bactype(type, _env, _opts) when type in [true, false] do
331+
:boolean
332+
end
333+
334334
defp field_typespec_to_bactype(type, _env, _opts) when is_atom(type) do
335335
type
336336
end
@@ -399,7 +399,8 @@ defmodule BACnet.BeamTypes do
399399
end
400400

401401
# Range expression x..y//z
402-
defp field_typespec_to_bactype({:.., _list, [value1, value2, step]}, _env, _opts) do
402+
defp field_typespec_to_bactype({range, _list, [value1, value2, step]}, _env, _opts)
403+
when range in [:.., :..//] do
403404
{:in_list, Enum.to_list(value1..value2//step)}
404405
end
405406

@@ -490,7 +491,7 @@ defmodule BACnet.BeamTypes do
490491
# BACnet.Array.t(...) - Error as more than a single parameter (subtype)
491492
_term ->
492493
raise CompileError,
493-
description: "BACnetArray must have a single parameter",
494+
description: "BACnetArray must have one or two parameters",
494495
file: env.file,
495496
line: env.line
496497
end
@@ -583,7 +584,7 @@ defmodule BACnet.BeamTypes do
583584

584585
# Remote type (such as String.t)
585586
defp map_beam_typespec_data(
586-
{:remote_type, _line, [{:atom, 0, module}, {:atom, 0, type}, args]},
587+
{:remote_type, _line, [{:atom, _num, module}, {:atom, _other, type}, args]},
587588
env,
588589
_spec_module,
589590
opts
@@ -605,10 +606,10 @@ defmodule BACnet.BeamTypes do
605606
{:ann_type, _line, [{:var, _line2, _name}, {:type, _line3, _type, _args} = spec]} ->
606607
map_beam_typespec_data(spec, env, spec_module, opts)
607608

608-
{:atom, 0, atom_name} ->
609+
{:atom, _num, atom_name} ->
609610
{:literal, atom_name}
610611

611-
{:remote_type, _line, [{:atom, 0, module}, {:atom, 0, type}, args]} ->
612+
{:remote_type, _line, [{:atom, _num, module}, {:atom, _other, type}, args]} ->
612613
field_typespec_to_bactype(
613614
{{:., [], [module, type]}, [], args},
614615
env,
@@ -637,12 +638,16 @@ defmodule BACnet.BeamTypes do
637638
end
638639

639640
# Map typespec (structs count as this)
640-
defp map_beam_typespec_data({:type, _line, :map, definition}, env, _spec_module, _opts) do
641+
defp map_beam_typespec_data({:type, _line, :map, definition}, env, _spec_module, _opts)
642+
when not is_atom(definition) do
641643
# Check if it's a struct, if so, return the module name
642644
module =
643645
Enum.find_value(definition, fn
644-
{:type, _line, :map_field_exact, [{:atom, 0, :__struct__}, {:atom, 0, module}]} -> module
645-
_term -> false
646+
{:type, _line, :map_field_exact, [{:atom, _num, :__struct__}, {:atom, _other, module}]} ->
647+
module
648+
649+
_term ->
650+
false
646651
end)
647652

648653
if module do
@@ -689,6 +694,12 @@ defmodule BACnet.BeamTypes do
689694
{subtype, subtypes}
690695
end
691696

697+
# Literal numbers or atoms
698+
defp map_beam_typespec_data({type, _num, val}, _env, _spec_module, _opts)
699+
when type in [:atom, :integer] do
700+
{:literal, val}
701+
end
702+
692703
# Custom local types
693704
defp map_beam_typespec_data(
694705
{:user_type, _line, type, args},

0 commit comments

Comments
 (0)