Can't figure out why dialyzer is restricting the integers for a struct value

I am trying to clean up all of the dialyzer warnings in my project, and I"m having a hard time fixing this last error.

lib/rtmp/protocol/handler.ex:204: The pattern #{‘message_type_id’:=19, ‘struct’:=‘Elixir.Rtmp.Protocol.RawMessage’} can never match the type #{‘struct’:=‘Elixir.Rtmp.Protocol.RawMessage’, ‘deserialization_system_time’:=‘nil’, ‘force_uncompressed’:=‘false’, ‘message_type_id’:=8 | 9 | 20, ‘payload’:=<<>>, ‘stream_id’:=non_neg_integer(), ‘timestamp’:=non_neg_integer()}

That section of code is:

defp get_csid_for_message_type(%RawMessage{message_type_id: 1}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 2}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 3}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 4}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 5}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 6}), do: 2
defp get_csid_for_message_type(%RawMessage{message_type_id: 18}), do: 3
defp get_csid_for_message_type(%RawMessage{message_type_id: 19}), do: 3
defp get_csid_for_message_type(%RawMessage{message_type_id: 9}), do: 21
defp get_csid_for_message_type(%RawMessage{message_type_id: 8}), do: 20
defp get_csid_for_message_type(%RawMessage{message_type_id: _}), do: 6  

Line 204 is the defp get_csid_for_message_type(%RawMessage{message_type_id: 19}), do: 21

This function is used in the following function:

def handle_cast({:send_message, message}, state) do
  raw_message = RawMessage.pack(message)
  csid = get_csid_for_message_type(raw_message)

  # ... rest snipped for brevity
end

So essentially it’s taking a %DetailedMessage{}, passing it into a RawMessage.pack() function to turn it into a %RawMessage{}, and then trying to get the csid for it. The definition for pack() is:

@doc "Packs a detailed RTMP message into a serializable raw message"
@spec pack(DetailedMessage.t) :: __MODULE__.t
def pack(message = %DetailedMessage{}) do
  {:ok, payload} = message.content.__struct__.serialize(message.content)

  %__MODULE__{
    timestamp: message.timestamp,
    stream_id: message.stream_id,
    message_type_id: get_message_type(message.content.__struct__),
    payload: payload,
    force_uncompressed: message.force_uncompressed
  }
end

# WARNING: We have to match on the module names themselves instead of
# a normal struct pattern match, otherwise we have circular references
# during compilation and it failes due to the callbacks
defp get_message_type(Rtmp.Protocol.Messages.SetChunkSize), do: 1
defp get_message_type(Rtmp.Protocol.Messages.Abort), do: 2
defp get_message_type(Rtmp.Protocol.Messages.Acknowledgement), do: 3
defp get_message_type(Rtmp.Protocol.Messages.UserControl), do: 4
defp get_message_type(Rtmp.Protocol.Messages.WindowAcknowledgementSize), do: 5
defp get_message_type(Rtmp.Protocol.Messages.SetPeerBandwidth), do: 6
defp get_message_type(Rtmp.Protocol.Messages.AudioData), do: 8
defp get_message_type(Rtmp.Protocol.Messages.VideoData), do: 9
defp get_message_type(Rtmp.Protocol.Messages.Amf0Data), do: 18
defp get_message_type(Rtmp.Protocol.Messages.Amf0Command), do: 20

Also, the type definition for RawMessage.t is:

@type t :: %__MODULE__{
  timestamp: non_neg_integer(),
  message_type_id: non_neg_integer(),
  stream_id: non_neg_integer(),
  force_uncompressed: false,
  deserialization_system_time: pos_integer() | nil,
  payload: <<>>
}

So as you can see I’m trying to tell dialyzer that RawMessage.message_type_id can be any non-negative integer yet dialyzer’s error message seems to be saying it thinks that message_type_id can only be 8, 9, or 20. What’s even more odd is that the get_csid_for_message_type function deals with many other situations where the message type id is not 8, 9, or 20 and yet it only seems to barf at the one where the number is 19.

Anyone have any insights on what might be causing this cause I’ve been staring at this for 30 minutes completely stumped.

If anyone wants to try for themselves you should be able to try it with this commit

1 Like

I didn’t try it out, but looking at your snippets, no clause of the function get_message_type returns the value 19, so perhaps that’s the problem?

Actually I’m an idiot and you are correct. That 19 is supposed to be a 20 which is why dialyzer is correctly flagging it.

Horray for a 2nd pair of eyes, thanks.