Skip to content

Commit 2cd0d3a

Browse files
committed
Resolve types from transitive import public deps
Closes #351.
1 parent 4d3ec99 commit 2cd0d3a

2 files changed

Lines changed: 85 additions & 1 deletion

File tree

lib/protobuf/protoc/cli.ex

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,56 @@ defmodule Protobuf.Protoc.CLI do
158158
Context.t()
159159
def find_types(%Context{} = ctx, descs, files_to_generate)
160160
when is_list(descs) and is_list(files_to_generate) do
161-
global_type_mapping =
161+
base_mapping =
162162
Map.new(descs, fn %Google.Protobuf.FileDescriptorProto{name: filename} = desc ->
163163
{filename, find_types_in_proto(ctx, desc, files_to_generate)}
164164
end)
165165

166+
# `import public` re-exports types from the imported file. A file that
167+
# depends on `shared.proto` should see types from anything `shared.proto`
168+
# publicly imports (transitively). We fold those types into each file's
169+
# mapping here so `get_dep_type_mapping/3` keeps working unchanged.
170+
public_deps_by_file =
171+
Map.new(descs, fn %Google.Protobuf.FileDescriptorProto{} = desc ->
172+
deps = desc.dependency
173+
public_deps = Enum.map(desc.public_dependency, &Enum.at(deps, &1))
174+
{desc.name, public_deps}
175+
end)
176+
177+
global_type_mapping =
178+
Map.new(base_mapping, fn {filename, mapping} ->
179+
transitive =
180+
collect_public_deps([filename], public_deps_by_file, MapSet.new([filename]))
181+
|> MapSet.delete(filename)
182+
183+
merged =
184+
Enum.reduce(transitive, mapping, fn other, acc ->
185+
Map.merge(acc, Map.get(base_mapping, other, %{}))
186+
end)
187+
188+
{filename, merged}
189+
end)
190+
166191
%{ctx | global_type_mapping: global_type_mapping}
167192
end
168193

194+
defp collect_public_deps([], _public_deps_by_file, visited), do: visited
195+
196+
defp collect_public_deps([filename | rest], public_deps_by_file, visited) do
197+
deps = Map.get(public_deps_by_file, filename, [])
198+
199+
{new_deps, visited} =
200+
Enum.reduce(deps, {[], visited}, fn dep, {to_visit, visited} ->
201+
if MapSet.member?(visited, dep) do
202+
{to_visit, visited}
203+
else
204+
{[dep | to_visit], MapSet.put(visited, dep)}
205+
end
206+
end)
207+
208+
collect_public_deps(new_deps ++ rest, public_deps_by_file, visited)
209+
end
210+
169211
defp find_types_in_proto(
170212
%Context{} = ctx,
171213
%Google.Protobuf.FileDescriptorProto{} = desc,

test/protobuf/protoc/cli_test.exs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,5 +226,47 @@ defmodule Protobuf.Protoc.CLITest do
226226
}
227227
}
228228
end
229+
230+
test "transitively folds in types from `import public` dependencies (#351)" do
231+
descs = [
232+
%FileDescriptorProto{
233+
name: "arm_status.proto",
234+
package: "mypackage.arm_status",
235+
enum_type: [%EnumDescriptorProto{name: "ArmStatus"}]
236+
},
237+
%FileDescriptorProto{
238+
name: "leg_status.proto",
239+
package: "mypackage.leg_status",
240+
enum_type: [%EnumDescriptorProto{name: "LegStatus"}]
241+
},
242+
%FileDescriptorProto{
243+
name: "shared.proto",
244+
package: "mypackage",
245+
dependency: ["arm_status.proto", "leg_status.proto"],
246+
public_dependency: [0, 1]
247+
},
248+
%FileDescriptorProto{
249+
name: "person.proto",
250+
package: "mypackage.person",
251+
dependency: ["shared.proto"],
252+
message_type: [%DescriptorProto{name: "Person"}]
253+
}
254+
]
255+
256+
mapping = find_types(%Context{}, descs, Enum.map(descs, & &1.name)).global_type_mapping
257+
258+
# `shared.proto` re-exports the enums from its public deps, so looking up
259+
# by `shared.proto` includes them.
260+
assert mapping["shared.proto"] == %{
261+
".mypackage.arm_status.ArmStatus" => %{type_name: "Mypackage.ArmStatus.ArmStatus"},
262+
".mypackage.leg_status.LegStatus" => %{type_name: "Mypackage.LegStatus.LegStatus"}
263+
}
264+
265+
# `person.proto` only directly depends on `shared.proto` but the publicly
266+
# re-exported types are still reachable.
267+
assert mapping["person.proto"] == %{
268+
".mypackage.person.Person" => %{type_name: "Mypackage.Person.Person"}
269+
}
270+
end
229271
end
230272
end

0 commit comments

Comments
 (0)