Skip to content

Fix symbols re-exported via import public not being found - #4636

Open
bufdev wants to merge 1 commit into
mainfrom
fix-public-import-resolution
Open

Fix symbols re-exported via import public not being found#4636
bufdev wants to merge 1 commit into
mainfrom
fix-public-import-resolution

Conversation

@bufdev

@bufdev bufdev commented Aug 4, 2026

Copy link
Copy Markdown
Member

Fixes #4633.

Since v1.68.0, which switched buf build to the new compiler, a symbol re-exported through import public is not always found by importers. protoc and v1.67.0 accept the same files.

From the reporter's repro:

// a.proto
import "b.proto";
message A { google.protobuf.Timestamp ts = 1; }

// b.proto
import public "act.proto";                      // imports timestamp.proto non-publicly
import public "c.proto";                        // imports timestamp.proto publicly

// c.proto
import public "google/protobuf/timestamp.proto";
proto/a.proto:5:3:cannot find `google.protobuf.Timestamp` in this scope

Nothing to do with well-known types; swapping the order of b.proto's two imports makes it compile.

Root cause

In protocompile, imports.Recurse classified each transitive import by looking only at whichever direct import happened to pull it in first, so a file reachable through several direct imports with differing public-ness could be misclassified. Public direct imports are ordered first specifically so the public path wins, but that does not disambiguate when both direct imports are public: act.proto is visited first, reaches timestamp.proto non-publicly, and timestamp.proto lands in the trailing non-public segment, so b.proto stops re-exporting it.

A second, related shape was also broken: a plain import of a file that is also re-exported by a public import. That file has to stay in a direct segment, and Transitive() derived public-ness purely from the segment offsets, so it could not represent the case at all.

Fixed in bufbuild/protocompile#754, which classifies every transitive import up front over all direct imports and records public-ness on the import rather than deriving it from segment offsets. This matches protoc's DescriptorBuilder::RecordPublicDependencies, which is a closure over public_dependency edges and therefore order-independent.

Testing

TestComparePublicImports builds testdata/publicimports and diffs the resulting FileDescriptorSet against real protoc. It covers both shapes independently (each has its own consumer file, so one working mechanism cannot mask the other breaking), and both fail before the protocompile fix:

testdata/publicimports/consumer_diamond.proto:11:3:cannot find `Leaf` in this scope
testdata/publicimports/consumer_shadowed.proto:10:3:cannot find `Leaf` in this scope

Beyond that, the fix was validated by differential fuzzing against protoc: 700 random import DAGs of up to 12 files, each file referencing every symbol protoc's rule says is visible (zero rejections), plus 838 pairwise accept/reject probes agreeing with protoc in both directions. The same fuzzer flags the bug on an unpatched build within ~30 seeds.

Note

go.mod currently pins protocompile to the branch commit for bufbuild/protocompile#754. Needs a re-pin to main once that merges.

Since v1.68.0, which switched to the new compiler, a symbol re-exported
through `import public` was not always found by importers, while protoc and
v1.67.0 accept the same files.

The compiler classified each transitive import by looking only at whichever
direct import happened to pull it in first, so a file reachable through
several direct imports with differing public-ness could be misclassified.
Fixed upstream in protocompile; bump the dependency and add a conformance
test covering the two affected shapes.

Fixes #4633.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The latest Buf updates on your PR. Results from workflow Buf CI / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 4, 2026, 9:33 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Since v1.68.0, a symbol that is re-exported via import public is not found by importers. v1.67.0 and protoc accept the same files.

2 participants