Skip to content

Feature/typed action input inference - #2915

Draft
Alt-iOS wants to merge 15 commits into
ash-project:mainfrom
Alt-iOS:feature/typed-action-input-inference
Draft

Feature/typed action input inference#2915
Alt-iOS wants to merge 15 commits into
ash-project:mainfrom
Alt-iOS:feature/typed-action-input-inference

Conversation

@Alt-iOS

@Alt-iOS Alt-iOS commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Contributor checklist

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Summary

This draft explores compile-time type checking for Ash action inputs inside changes, validations, and preparations using Elixir’s set-theoretic type system.

Ash already knows each action’s arguments, accepted attributes, types, and nilability. This change exposes that information to the compiler through temporary type-witness modules.

How it works

  • Spark lifts inline DSL callback bodies through a generic callback.
  • Ash builds specialized %Ash.Changeset{} or %Ash.Query{} witnesses for each action.
  • A Mix compiler runs after normal Elixir compilation to check dispatched callbacks, whose modules compile before Ash knows which actions use them.
  • Witness modules exist only during compilation and do not modify application modules or runtime behaviour.
  • Incremental compilation fingerprints resources and recompiles only affected witnesses. --force recompiles all witnesses.

This depends on the accompanying Spark change that introduces the generic callback-lifting hook.

Currently supported

  • Exact action argument and accepted-attribute keys.
  • Primitive value types and nilability.
  • Constrained maps.
  • Embedded resources and arrays of embedded resources.
  • Inputs passed through manage_relationship.
  • Inline and dispatched CRUD changes.
  • change/3, atomic/3, and batch_change/3.
  • Validations and preparations.
  • Literal callback options.

Example diagnostics

Unknown changeset field:

warning: Unknown Ash.Changeset field :value_test

In:
  MyApp.Dispatch.Changes.Lines.change/3

Ash.Changeset has no field named :value_test.
Action arguments: [:allocations]
Accepted attributes: [:idempotency_key, :dispatched_at]

Unknown action argument:

warning: unknown key .unknown_argument in expression:

    changeset.arguments.unknown_argument

the given type does not have the given key:

    %{
      quantity: integer(),
      reference: nil or binary()
    }

Ash action context:
Action arguments: [:quantity, :reference]
Accepted attributes: [:label, :count]

Incorrect value type:

changeset.arguments.quantity <> ""

Ash declares quantity as integer(), while <>/2 requires a binary, so Elixir reports the incompatible types during compilation.

Current limitations

  • Manual generic-action modules.
  • Calculations.
  • Arbitrary loaded relationship fields.
  • Runtime-selected or otherwise dynamic callbacks.
  • Unconstrained maps remain open.
  • Custom Ash.Type implementations that cannot be reduced to a known primitive or struct remain dynamic.
  • Runtime constraints such as string lengths and numeric ranges remain runtime validations.

Some diagnostics produced through local inline callbacks are technically correct but difficult to read because Elixir infers the callback requirement backward and reports the conflict at the call boundary. Improving those messages is possible follow-up work.

Verification

  • Targeted type-inference tests: 19 passed.
  • Full Ash suite: 4,094 passed.
  • Tested in a separate multi-file application containing dispatched callbacks, inline callbacks, embedded resources, managed relationships, validations, preparations, atomic changes, and batch changes.

A full demo of all available diagnostics is at https://github.com/Alt-iOS/ash_type_inference_demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant