Skip to content

Commit 178643f

Browse files
committed
Do not discard nil on protocol concat, closes #14311 (#14314)
1 parent 75677b9 commit 178643f

File tree

3 files changed

+35
-11
lines changed

3 files changed

+35
-11
lines changed

lib/elixir/lib/module.ex

+5-1
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,8 @@ defmodule Module do
946946
@doc """
947947
Concatenates two aliases and returns a new alias.
948948
949-
It handles binaries and atoms.
949+
It handles binaries and atoms. If one of the aliases
950+
is nil, it is discarded.
950951
951952
## Examples
952953
@@ -956,6 +957,9 @@ defmodule Module do
956957
iex> Module.concat(Foo, "Bar")
957958
Foo.Bar
958959
960+
iex> Module.concat(Foo, nil)
961+
Foo
962+
959963
"""
960964
@spec concat(binary | atom, binary | atom) :: atom
961965
def concat(left, right)

lib/elixir/lib/protocol.ex

+25-8
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ defmodule Protocol do
347347
end
348348

349349
defp assert_impl!(protocol, base, extra) do
350-
impl = Module.concat(protocol, base)
350+
impl = Protocol.__concat__(protocol, base)
351351

352352
try do
353353
Code.ensure_compiled!(impl)
@@ -678,7 +678,7 @@ defmodule Protocol do
678678
end
679679

680680
defp load_impl(protocol, for) do
681-
Module.concat(protocol, for)
681+
Protocol.__concat__(protocol, for)
682682
end
683683

684684
# Finally compile the module and emit its bytecode.
@@ -831,7 +831,7 @@ defmodule Protocol do
831831
# Define the implementation for built-ins
832832
:lists.foreach(
833833
fn {guard, mod} ->
834-
target = Module.concat(__MODULE__, mod)
834+
target = Protocol.__concat__(__MODULE__, mod)
835835

836836
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
837837
case Code.ensure_compiled(unquote(target)) do
@@ -875,7 +875,7 @@ defmodule Protocol do
875875

876876
# Internal handler for Structs
877877
Kernel.defp struct_impl_for(struct) do
878-
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
878+
case Code.ensure_compiled(Protocol.__concat__(__MODULE__, struct)) do
879879
{:module, module} -> module
880880
{:error, _} -> unquote(any_impl_for)
881881
end
@@ -948,7 +948,7 @@ defmodule Protocol do
948948
quote do
949949
protocol = unquote(protocol)
950950
for = unquote(for)
951-
name = Module.concat(protocol, for)
951+
name = Protocol.__concat__(protocol, for)
952952

953953
Protocol.assert_protocol!(protocol)
954954
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
@@ -994,7 +994,7 @@ defmodule Protocol do
994994
else
995995
# TODO: Deprecate this on Elixir v1.22+
996996
assert_impl!(protocol, Any, extra)
997-
{Module.concat(protocol, Any), [for, Macro.struct!(for, env), opts]}
997+
{Protocol.__concat__(protocol, Any), [for, Macro.struct!(for, env), opts]}
998998
end
999999

10001000
# Clean up variables from eval context
@@ -1006,7 +1006,7 @@ defmodule Protocol do
10061006
else
10071007
__ensure_defimpl__(protocol, for, env)
10081008
assert_impl!(protocol, Any, extra)
1009-
impl = Module.concat(protocol, Any)
1009+
impl = Protocol.__concat__(protocol, Any)
10101010

10111011
funs =
10121012
for {fun, arity} <- protocol.__protocol__(:functions) do
@@ -1031,7 +1031,11 @@ defmodule Protocol do
10311031
def __impl__(:for), do: unquote(for)
10321032
end
10331033

1034-
Module.create(Module.concat(protocol, for), [quoted | funs], Macro.Env.location(env))
1034+
Module.create(
1035+
Protocol.__concat__(protocol, for),
1036+
[quoted | funs],
1037+
Macro.Env.location(env)
1038+
)
10351039
end
10361040
end)
10371041
end
@@ -1070,4 +1074,17 @@ defmodule Protocol do
10701074
is_reference: Reference
10711075
]
10721076
end
1077+
1078+
@doc false
1079+
def __concat__(left, right) do
1080+
String.to_atom(
1081+
ensure_prefix(Atom.to_string(left)) <> "." <> remove_prefix(Atom.to_string(right))
1082+
)
1083+
end
1084+
1085+
defp ensure_prefix("Elixir." <> _ = left), do: left
1086+
defp ensure_prefix(left), do: "Elixir." <> left
1087+
1088+
defp remove_prefix("Elixir." <> right), do: right
1089+
defp remove_prefix(right), do: right
10731090
end

lib/elixir/test/elixir/protocol_test.exs

+5-2
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,18 @@ defmodule ProtocolTest do
106106
assert Sample.impl_for(%ImplStruct{}) == Sample.ProtocolTest.ImplStruct
107107
assert Sample.impl_for(%ImplStructExplicitFor{}) == Sample.ProtocolTest.ImplStructExplicitFor
108108
assert Sample.impl_for(%NoImplStruct{}) == nil
109+
assert is_nil(Sample.impl_for(%{__struct__: nil}))
109110
end
110111

111112
test "protocol implementation with Any and struct fallbacks" do
112113
assert WithAny.impl_for(%NoImplStruct{}) == WithAny.Any
113-
# Derived
114-
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
114+
assert WithAny.impl_for(%{__struct__: nil}) == WithAny.Any
115115
assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
116116
assert WithAny.impl_for(%{}) == WithAny.Map
117117
assert WithAny.impl_for(self()) == WithAny.Any
118+
119+
# Derived
120+
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
118121
end
119122

120123
test "protocol not implemented" do

0 commit comments

Comments
 (0)