ash-project / ash

A declarative, extensible framework for building Elixir applications.
https://www.ash-hq.org
MIT License
1.63k stars 218 forks source link

Ash.CustomExpression compilation error, anonymous function not allowed #1571

Closed siassaj closed 2 weeks ago

siassaj commented 2 weeks ago

Describe the bug Ash.CustomExpression with a passed function raises compile time error

== Compilation error in file lib/expressions/levenshtein_distance.ex ==
** (RuntimeError) The only kind of anonymous functions allowed in expressions are in the format `&Module.function/arity`.

Got: &__MODULE__.levenshtein/2

    (ash 3.4.39) lib/ash/expr/expr.ex:763: Ash.Expr.do_expr/2
    (elixir 1.17.2) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (ash 3.4.39) lib/ash/expr/expr.ex:793: Ash.Expr.do_expr/2
    (ash 3.4.39) expanding macro: Ash.Expr.expr/1
    lib/expressions/levenshtein_distance.ex:21: Domain.Expressions.LevenshteinDistance.expression/2

To Reproduce

  1. implement lib/expressions/levenshtein_distance.ex described in the docs for Ash.CustomExpression

    defmodule Domain.Expressions.LevenshteinDistance do
    use Ash.CustomExpression,
    name: :levenshtein,
    arguments: [
      [:string, :string]
    ]
    
    def expression(AshPostgres.DataLayer, [left, right]) do
    {:ok, expr(fragment("levenshtein(?, ?)", left, right))}
    end
    
    # It is good practice to always define an expression for `Ash.DataLayer.Simple`,
    # as that is what Ash will use to run your custom expression in Elixir.
    # This allows us to completely avoid communicating with the database in some cases.
    
    def expression(data_layer, [left, right])
      when data_layer in [
             Ash.DataLayer.Ets,
             Ash.DataLayer.Simple
           ] do
    {:ok, expr(fragment(&__MODULE__.levenshtein/2, left, right))}
    end
    
    # always define this fallback clause as well
    def expression(_data_layer, _args), do: :unknown
    
    @doc "Computes the levenshtein distance of two strings"
    def levenshtein(left, right) do
    # ......
    end
    end

    =>

== Compilation error in file lib/expressions/levenshtein_distance.ex ==
** (RuntimeError) The only kind of anonymous functions allowed in expressions are in the format `&Module.function/arity`.

Got: &__MODULE__.levenshtein/2

    (ash 3.4.39) lib/ash/expr/expr.ex:763: Ash.Expr.do_expr/2
    (elixir 1.17.2) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
    (ash 3.4.39) lib/ash/expr/expr.ex:793: Ash.Expr.do_expr/2
    (ash 3.4.39) expanding macro: Ash.Expr.expr/1
    lib/expressions/levenshtein_distance.ex:21: Domain.Expressions.LevenshteinDistance.expression/2
  1. Add to config/config.exs

    config :ash, :custom_expressions, [Domain.Expressions.LevenshteinDistance]
  2. Bonus round; use the absolute module name instead of __MODULE__

    def expression(data_layer, [left, right])
      when data_layer in [
             Ash.DataLayer.Ets,
             Ash.DataLayer.Simple
           ] do
    {:ok, expr(fragment(&Domain.Expressions.LevenshteinDistance.levenshtein/2, left, right))}
    end

    =>

    
    == Compilation error in file lib/expressions/levenshtein_distance.ex ==
    ** (RuntimeError) The only kind of anonymous functions allowed in expressions are in the format `&Module.function/arity`.

Got: &Domain.Expressions.LevenshteinDistance.levenshtein/2

(ash 3.4.39) lib/ash/expr/expr.ex:763: Ash.Expr.do_expr/2
(elixir 1.17.2) lib/enum.ex:1703: Enum."-map/2-lists^map/1-1-"/2
(ash 3.4.39) lib/ash/expr/expr.ex:793: Ash.Expr.do_expr/2
(ash 3.4.39) expanding macro: Ash.Expr.expr/1
lib/expressions/levenshtein_distance.ex:21: Domain.Expressions.LevenshteinDistance.expression/2


**Expected behavior**
The custom expression should compile and the expression should be usable in a resource.

**Runtime**
 - Elixir version
   1.17.2
 - Erlang version
   Erlang/OTP 27 [erts-15.0.1]
 - OS
   Ubuntu 22.04.5 LTS
 - Ash version
   3.4.39
 - any related extension versions
   ash_postgres: 2.4.12