DEV Community

Matthew McGarvey
Matthew McGarvey

Posted on • Edited on

Macro Tips: TypeDeclaration

When working with macros, you might have one that allows specifying a name, type, and an optional default value.

It might look something like this

param query : String = "default query"
Enter fullscreen mode Exit fullscreen mode

The code for the macro will take in one parameter and that parameter will be a TypeDeclaration.

macro param(type_decl)
  # ...
end
Enter fullscreen mode Exit fullscreen mode

If you imagine that this param macro is for a web framework and used to define the query params for an endpoint, it makes sense that it would raise an error if the query is not nilable and no default value is given in the method that it defines.

The code for that could be (forgive me for this mess)

macro param(type_decl)
  def {{ type_decl.var }} : {{ type_decl.type }}
    {% is_nilable_type = type_decl.type.is_a?(Union) %}

    val = params.get?(:{{ type_declaration.var.id }})

    return val unless val.nil?

    {% if is_nilable_type %}
      {{ type_declaration.value || nil }}
    {% else %}
      default_or_nil = {{ type_declaration.value || nil }}
      if default_or_nil.nil?
        raise MissingParamError.new("{{ type_declaration.var.id }}")
      else
        default_or_nil
      end
    {% end %}
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Can you see the problem with this?

What if a param was this instead param show_deleted : Bool = false?

The problem is the line default_or_nil = {{ type_declaration.value || nil }}. It means that any time the value is not provided, it uses the default which is false and is obviously falsey which means that default_or_nil evaluates to nil and ends up raising the error.

The solution is to check if the value is a Nop instead of checking the truthiness of the default value since value is a Nop when it isn't defined.

Instead of

default_or_nil = {{ type_declaration.value || nil }}
Enter fullscreen mode Exit fullscreen mode

it should be

default_or_nil = {{ type_declaration.value.is_a?(Nop) ? nil : type_declaration.value }}
Enter fullscreen mode Exit fullscreen mode

Here's the relevant PR for the fix made in Lucky.

Top comments (0)