From 10900ca0f3b9bdf63c37cd41bc85e858d9fd7c21 Mon Sep 17 00:00:00 2001 From: Martin Schut Date: Tue, 7 Feb 2023 15:29:25 +0100 Subject: [PATCH] Add opaque typespecs --- lib/strukt.ex | 14 +++++++++----- lib/typespec.ex | 12 +++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/strukt.ex b/lib/strukt.ex index 5d92d04..df26b6e 100644 --- a/lib/strukt.ex +++ b/lib/strukt.ex @@ -31,8 +31,8 @@ defmodule Strukt do NOTE: It is recommended that if you need to perform custom validations, that you use the `validation/1` and `validation/2` facility for performing custom - validations in a module or function, and if necessary, override `c:validate/1` - instead of performing validations in this callback. If you need to override this + validations in a module or function, and if necessary, override `c:validate/1` + instead of performing validations in this callback. If you need to override this callback specifically for some reason, make sure you call `super/2` at some point during your implementation to ensure that validations are run. """ @@ -242,9 +242,11 @@ defmodule Strukt do [] end + opaque_fields = Enum.any?(special_attrs, fn {:@, _, [{attr, _, [value]}]} -> attr == :opaque_fields &&!!value end) + fields = Strukt.Field.parse(fields) - define_struct(env, name, meta, moduledoc, derives, schema_attrs, fields, body) + define_struct(env, name, meta, moduledoc, derives, opaque_fields, schema_attrs, fields, body) end # This clause handles the edge case where the definition only contains @@ -252,10 +254,10 @@ defmodule Strukt do defp define_struct(env, name, {type, _, _} = field) when is_supported(type) do fields = Strukt.Field.parse([field]) - define_struct(env, name, [], nil, [], [], fields, []) + define_struct(env, name, [], nil, [], false, [], fields, []) end - defp define_struct(_env, name, meta, moduledoc, derives, schema_attrs, fields, body) do + defp define_struct(_env, name, meta, moduledoc, derives, opaque_fields, schema_attrs, fields, body) do # Extract macros which should be defined at the top of the module {macros, body} = Enum.split_with(body, fn @@ -356,6 +358,7 @@ defmodule Strukt do end @schema_name Macro.underscore(__MODULE__) + @opaque_fields unquote(opaque_fields) @validated_fields unquote(validated_fields) @cast_embed_fields unquote(Macro.escape(cast_embed_fields)) @@ -491,6 +494,7 @@ defmodule Strukt do typespec_ast = Strukt.Typespec.generate(%Strukt.Typespec{ caller: __MODULE__, + opaque: @opaque_fields, info: @validated_fields, fields: @cast_fields, embeds: @cast_embed_fields diff --git a/lib/typespec.ex b/lib/typespec.ex index 240b2ac..608bc32 100644 --- a/lib/typespec.ex +++ b/lib/typespec.ex @@ -1,11 +1,13 @@ defmodule Strukt.Typespec do @moduledoc false - defstruct [:caller, :info, :fields, :embeds] + defstruct [:caller, :opaque, :info, :fields, :embeds] @type t :: %__MODULE__{ # The module where the struct is being defined caller: module, + # Defines whether the typespec should be opaque or the default type + opaque: boolean, # Metadata about all fields in the struct info: %{optional(atom) => map}, # A list of all non-embed field names @@ -45,7 +47,7 @@ defmodule Strukt.Typespec do * `fields` - This is a list of all field names which are defined via `field/3` * `embeds` - This is a list of all field names which are defined via `embeds_one/3` or `embeds_many/3` """ - def generate(%__MODULE__{caller: caller, info: info, fields: fields, embeds: embeds}) do + def generate(%__MODULE__{caller: caller, opaque: opaque, info: info, fields: fields, embeds: embeds}) do # Build up the AST for each field's type spec fields = fields @@ -87,7 +89,11 @@ defmodule Strukt.Typespec do # Join all fields together struct_fields = fields ++ embeds - quote(context: caller, do: @type(t :: %__MODULE__{unquote_splicing(struct_fields)})) + if opaque do + quote(context: caller, do: @opaque(t :: %__MODULE__{unquote_splicing(struct_fields)})) + else + quote(context: caller, do: @type(t :: %__MODULE__{unquote_splicing(struct_fields)})) + end end defp primitive(atom, args \\ []) when is_atom(atom) and is_list(args),