phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.14k stars 919 forks source link

fix: make Phoenix.LiveView.JS.t transparent #3295

Closed bklebe closed 3 months ago

bklebe commented 3 months ago

Phoenix.LiveView.JS.t is already exposed as a struct by the CoreComponents generator: https://github.com/phoenixframework/phoenix/blob/2e06455a4041965da133cd23a650245f53d6800c/priv/templates/phx.gen.live/core_components.ex#L602.

This PR makes the struct type itself transparent while keeping its fields opaque so that functions operating on Phoenix.LiveView.JS.t can be properly specced. Otherwise, speccing the CoreComponents JS functions as such:

@spec show(String.t()) :: Phoenix.LiveView.JS.t()
@spec show(Phoenix.LiveView.JS.t(), String.t()) :: Phoenix.LiveView.JS.t()
def show(js \\ %JS{}, selector) do

causes the following Dialyzer error:

The @spec for WebWeb.CoreComponents.show/1 has an opaque
subtype Phoenix.LiveView.JS.t() which is violated by the success typing.

Success typing:
(_) :: %Phoenix.LiveView.JS{:ops => [any(), ...], _ => _}

This change also refactors usage of the struct update syntax in put_op to use Kernel.struct!/2, which appears to be necessary otherwise Dialyzer becomes convinced that the return type of functions using it is:

#{'__struct__':='Elixir.Phoenix.LiveView.JS', 'ops':=[any(),...], _=>_}

My apologies if some or all of this is a Dialyzer or Dialyxir bug, or if the proper spec for functions like CoreComponents.show/1 is something else.

josevalim commented 3 months ago

:green_heart: :blue_heart: :purple_heart: :yellow_heart: :heart: