Skip to content

Commit 30add2c

Browse files
Better codegen for mutually recursive dependencies (#264)
* Better codegen for mutually recursive dependencies (#264) * v1.1.0 Co-authored-by: Nick Robinson <npr251@gmail.com>
1 parent 2e80936 commit 30add2c

16 files changed

+1117
-226
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ keywords = ["protobuf", "protoc"]
44
license = "MIT"
55
desc = "Julia protobuf implementation"
66
authors = ["Tomáš Drvoštěp <tomas.drvostep@gmail.com>"]
7-
version = "1.0.16"
7+
version = "1.1.0"
88

99
[deps]
1010
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
@@ -17,7 +17,7 @@ Aqua = "0.8"
1717
BufferedStreams = "1.2"
1818
Dates = "1"
1919
EnumX = "1"
20-
JET = "0.4, 0.5, 0.6, 0.7, 0.8"
20+
JET = "0.4, 0.9"
2121
TOML = "1"
2222
Test = "1"
2323
julia = "1.6"

src/codegen/CodeGenerators.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ struct Context
3838
proto_file::ProtoFile
3939
proto_file_path::String
4040
file_map::Dict{String,ResolvedProtoFile}
41-
_curr_cyclic_defs::Set{String}
42-
_toplevel_name::Ref{String}
41+
_types_and_oneofs_requiring_type_params::Dict{String,Vector{Tuple{Bool,String}}}
42+
_remaining_cyclic_defs::Set{String}
43+
_toplevel_raw_name::Ref{String}
4344
transitive_imports::Set{String}
4445
options::Options
4546
end
@@ -179,10 +180,10 @@ function _protojl(
179180
# the files are read in order that respect these implicit dependencies.
180181
resolve_inter_package_references!(parsed_files, options)
181182
sorted_files, cyclical_imports = _topological_sort(parsed_files)
182-
!isempty(cyclical_imports) && throw(error(string(
183+
!isempty(cyclical_imports) && error(string(
183184
"Detected cyclical dependency among following imports: $cyclical_imports, ",
184185
"possibly, the individual files are resolvable, but their `package`s are not."
185-
)))
186+
))
186187
sorted_files = [parsed_files[sorted_file] for sorted_file in sorted_files]
187188
n = Namespaces(sorted_files, output_directory, parsed_files)
188189
for m in n.non_namespaced_protos
@@ -198,4 +199,4 @@ end
198199

199200
export protojl
200201

201-
end # CodeGenerators
202+
end # CodeGenerators

src/codegen/decode_methods.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ function generate_decode_method(io, t::MessageType, ctx::Context)
103103
println(io, " " ^ (3 - !has_fields), "PB.skip(d, wire_type)")
104104
has_fields && println(io, " end")
105105
println(io, " end")
106-
print(io, " return ", jl_typename(t, ctx), "(")
106+
print(io, " return ")
107+
_maybe_parametrize_constructor_to_handle_oneofs(io, t, ctx)
108+
print(io, "(")
107109
for (i, field) in enumerate(t.fields)
108110
print(io, jl_fieldname_deref(field, ctx))
109111
i < n && (print(io, ", "))
110112
end
111113
println(io, ")")
112114
println(io, "end")
113-
end
115+
end

src/codegen/defaults.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ function jl_default_value(@nospecialize(field::FieldType), ctx::Context)
1515
end
1616

1717
function _is_optional_referenced_message(field::Union{FieldType{ReferencedType},GroupType}, ctx::Context)
18-
struct_name = ctx._toplevel_name[]
19-
(field.type.name == struct_name || field.type.name in ctx._curr_cyclic_defs) && return true
18+
struct_name = ctx._toplevel_raw_name[]
19+
(field.type.name == struct_name || field.type.name in ctx._remaining_cyclic_defs) && return true
2020
if field.label == Parsers.OPTIONAL || field.label == Parsers.DEFAULT
2121
return !_should_force_required(string(struct_name, ".", jl_fieldname(field)), ctx)
2222
end

src/codegen/metadata_methods.jl

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,79 @@ function maybe_generate_kwarg_constructor_method(io, t::MessageType, ctx::Contex
102102
print(io, jl_fieldname(field), !isnothing(val) ? string(" = ", val) : "")
103103
i < n && print(io, ", ")
104104
end
105-
print(io, ") = $(type_name)(")
105+
print(io, ") = ")
106+
_maybe_parametrize_constructor_to_handle_oneofs(io, t, ctx)
107+
108+
print(io, "(")
106109
for (i, field) in enumerate(t.fields)
107110
print(io, jl_fieldname(field))
108111
i < n && print(io, ", ")
109112
end
110113
println(io, ')')
111-
end
114+
end
115+
116+
function maybe_generate_constructor_for_type_alias(io, t::MessageType, ctx::Context)
117+
#=
118+
Type aliases are supposed to point to _concretized_ stub definitions of the types which
119+
we couldn't generate the normal way due to mutually recursive type dependencies. In case
120+
the stub definition also contains oneof fields and we parametrize oneofs, the alias would
121+
no longer be concretized and would would fail to type-match concrete OneOf instances
122+
as valid inputs. Here we generate a constructor for the alias that that provides the
123+
concrete types of all parameters to the stub definition. E.g:
124+
125+
```proto
126+
message A { B b = 1; }
127+
message B { oneof x { int32 i = 1; A a = 2; } }
128+
```
129+
130+
would generate the following types, constructors and aliases:
131+
132+
```julia
133+
using ProtoBuf: OneOf
134+
abstract type var"##Abstract#A" end
135+
abstract type var"##Abstract#B" end
136+
137+
struct var"##Stub#A"{T1<:var"##Abstract#B"} <: var"##Abstract#A"
138+
b::Union{Nothing,T1}
139+
end
140+
struct var"##Stub#B"{T1<:Union{Nothing,OneOf{<:Union{Int32,var"##Abstract#A"}}}} <: var"##Abstract#B"
141+
x::T1
142+
end
143+
144+
const A = var"##Stub#A"{var"##Stub#B"{<:Union{Nothing,<:OneOf}}}
145+
const B = var"##Stub#B"{<:Union{Nothing,<:OneOf}}
146+
B(x) = var"##Stub#B"{typeof(x)}(x) # <- This is what we generate in this method
147+
```
148+
Without the last line, we wouldn't be able to call B on concrete OneOf instances:
149+
```julia
150+
julia> B(OneOf(:i, Int32(1)))
151+
ERROR: MethodError: no method matching (B)(::OneOf{Int32})
152+
Stacktrace:
153+
[1] top-level scope
154+
@ REPL[9]:1
155+
156+
julia> B(x) = var"##Stub#B"{typeof(x)}(x)
157+
B (alias for var"##Stub#B"{<:Union{Nothing, var"#s31"} where var"#s31"<:OneOf})
158+
159+
julia> B(OneOf(:i, Int32(1)))
160+
B{OneOf{Int32}}(OneOf{Int32}(:i, 1))
161+
```
162+
=#
163+
!(ctx.options.parametrize_oneofs && t.has_oneof_field) && return
164+
n = length(t.fields)
165+
type_name = safename(t)
166+
print(io, "$(type_name)(")
167+
for (i, field) in enumerate(t.fields)
168+
print(io, jl_fieldname(field))
169+
i < n && print(io, ", ")
170+
end
171+
print(io, ") = ")
172+
_maybe_parametrize_constructor_to_handle_oneofs(io, t, ctx)
173+
174+
print(io, "(")
175+
for (i, field) in enumerate(t.fields)
176+
print(io, jl_fieldname(field))
177+
i < n && print(io, ", ")
178+
end
179+
println(io, ')')
180+
end

src/codegen/names.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const JULIA_RESERVED_KEYWORDS = Set{String}([
22
"abstract",
3+
"AbstractProtoBufMessage",
34
"Any",
45
"Array",
56
"baremodule",
@@ -19,6 +20,7 @@ const JULIA_RESERVED_KEYWORDS = Set{String}([
1920
"end",
2021
"Enum",
2122
"export",
23+
"Expr",
2224
"false",
2325
"finally",
2426
"for",
@@ -64,9 +66,11 @@ function _safename(name::AbstractString)
6466
end
6567
end
6668

67-
abstract_type_name(name::AbstractString) = string("var\"##Abstract", name, '"')
69+
abstract_type_name(name::AbstractString) = string("var\"##Abstract#", name, "\"")
6870

6971
jl_fieldname(@nospecialize(f::AbstractProtoFieldType)) = _safename(f.name)
7072
jl_fieldname(f::GroupType) = _safename(f.field_name)
7173

7274
_safe_namespace_string(ns::AbstractVector{<:AbstractString}) = string("var\"#$(first(ns))\"", '.', join(@view(ns[2:end]), '.'))
75+
76+
stub_type_name(x) = string("var\"##Stub#", x, "\"")

0 commit comments

Comments
 (0)