feat: opt-in named types for Ecto.Enum fields (#39) - #63
Merged
Merged
Conversation
bamorim
force-pushed
the
feat-39-enum-types
branch
from
August 24, 2026 09:54
6fda359 to
90f9700
Compare
Add an opt-in `additional_types` schema-level option to typed_schema
and typed_embedded_schema. When enabled, each Ecto.Enum field defines a
public named type with the union of its values (union of atom keys for
keyword values, element union for {:array, Ecto.Enum}), so it can be
referenced from other modules' specs as MySchema.field_name().
Fields named t and fields whose values can't be resolved to a list of
atoms are silently skipped. Also add a fallback clause to
disjunction_typespec/1 so unresolvable values fall back to any()
instead of raising.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Allow enabling additional_types globally via compile-time application config (config :typed_ecto_schema, additional_types: true), with the schema-level option taking precedence in both directions, following the same Application.compile_env pattern as the polymorphic_embed flag. When both the PolymorphicEmbed integration and additional_types are enabled, polymorphic_embeds_one/many fields also emit a named type with the union of the modules in their :types option, reusing the union AST the syntax sugar already builds (skipping the any() fallback for unresolvable types and fields named t). Rename the internal enum_types accumulator to additional_types since it is no longer enum-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bamorim
force-pushed
the
feat-39-enum-types
branch
from
August 25, 2026 15:32
90f9700 to
da4d0a1
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in, experimental
additional_typesschema-level option totyped_schemaandtyped_embedded_schema(alongside the existing:null,:enforceand:opaqueoptions). When enabled, eachEcto.Enumfield defines a public named type with the union of its values, so it can be referenced from other modules' specs:Two follow-up additions after rebasing onto 0.4.4 + #64:
config :typed_ecto_schema, additional_types: true, read withApplication.compile_env/4like feat: support polymorphic_embed's polymorphic_embeds_one/many (#40) #64'spolymorphic_embedflag, so schemas recompile when it changes. The schema-level option wins in both directions (additional_types: falseopts a schema out of the global default).additional_typesare enabled,polymorphic_embeds_one(:channel, types: [sms: SMS, email: Email])also defines@type channel() :: SMS.t() | Email.t()(element union for_many, mirroring the{:array, Ecto.Enum}behavior). This reuses the module-union AST the syntax sugar already builds for the inline typespec, so module aliases are still never resolved; theany()fallback for unresolvabletypes:emits no named type.Behavior
values: [foo: 1, bar: 2]) generate the union of the atom keys (:foo | :bar).{:array, Ecto.Enum}fields generate the union of the element values, since that's what's useful in other specs.values: @role_values) resolve fine, since options are evaluated in the module body before the type builder runs — so the exact example from the issue gets a named type too.:valuescan't be resolved to a list of atoms, and fields namedt(which would collide with the schema's ownt/0). Other name collisions (e.g. a field named after a user-defined or built-in type) error naturally at compile time; this is documented in the moduledoc and README.EctoTypeMapper: an earlier revision added a fallback clause for unresolvable:values, but after rebasing onto master that is already covered byenum_type/1from fix: handle empty and non-literal Ecto.Enum values (#57) #62, so it was dropped.Tests
Additions: global config on (named types emitted with no schema option), schema-level
additional_types: falseoverriding the global config,polymorphic_embeds_one/_manymodule unions, unresolvable polymorphictypes:emitting nothing.{:array, Ecto.Enum}→ element uniont/ non-enum fields → skippedtyped_embedded_schemasupportt/0exportedmix test && mix credo --strict && mix dialyzerall pass.Closes #39
🤖 Generated with Claude Code