String text = ”data type in Elixir”;

I think you will see over time that there are very few scenarios where this is syntax you would really write. In most cases, you will be invoking these functions, and evaluating them as part of a with or case clause. The %MyStruct{} = my_var = foo() pattern is not necessary. This pattern match will raise a MatchError only if the result of foo() is not a MyStruct struct.

In most real use scenarios, you’re going to want to do some more detailed matching, and probably have an execution path in case that match fails. So more realistic scenarios will look more like this:

case foo() do
 %MyStruct{val: x} when x < 0 ->
   "Value is less than zero"

 _ ->
    "No value found"
end

or

with %MyStruct{} = ms <- foo() do
  OtherModule.process(ms)
else
   result ->
      Logger.error("foo() did not return valid results: #{inspect(result)}.")
end

I agree though that IDEs can be very flaky about actually showing the correct spec / docs in editor. I have many issues with this in IntelliJ, so typically just do a split panel setup with references on the on one half. I miss the robustness of JVM docs / autocomplete.