Skip to content

Reverse Interop: Nested namespace support#6940

Merged
jonmeow merged 15 commits intocarbon-language:trunkfrom
dwblaikie:reverse_interop_nested_namespace
Mar 24, 2026
Merged

Reverse Interop: Nested namespace support#6940
jonmeow merged 15 commits intocarbon-language:trunkfrom
dwblaikie:reverse_interop_nested_namespace

Conversation

@dwblaikie
Copy link
Contributor

@dwblaikie dwblaikie commented Mar 19, 2026

Start recording the clang::DeclContext* -> InstId mapping for use in later operations.

The test update includes removing the initial fail_* test because I hadn't thought about the use of namespace aliases as a way to test for the presence of a namespace without the failure caused by not finding the thing inside the namespace.

@dwblaikie dwblaikie requested a review from a team as a code owner March 19, 2026 22:17
@dwblaikie dwblaikie requested review from jonmeow and removed request for a team March 19, 2026 22:17
auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;

private:
auto MapInstIdToClangDecl(clang::DeclContext& decl_context,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pardon if I ask a silly question... Should this class have comments? When is it used and what are its semantics?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sure. Added some comments in 2d9c34e

// Provides clang ASTs representing Carbon SemIR entities.
class CarbonExternalASTSource : public clang::ExternalASTSource {
 public:
  explicit CarbonExternalASTSource(Context* context,
                                   clang::ASTContext* ast_context)
      : context_(context), ast_context_(ast_context) {}

  // Look up decls for `decl_name` inside `decl_context`, adding the decls to
  // `decl_context`. Returns true if any decls were added.
  auto FindExternalVisibleDeclsByName(

Commenting StartTranslationUnit didn't seem especially worthwhile - the contract is fairly self-explanatory, and it doesn't seem helpful to comment the current implementation compared to reading it & it'd get out of date/just encode whatever we do in there, not some broader contract that I can think of.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, maybe just "// See ExternalASTSource" -- I'd missed the override at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure bd9bd21

Comment on lines +396 to +397
auto decl_context_inst_id =
scope_mapping.Lookup(const_cast<clang::DeclContext*>(decl_context));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the call order here, can you document what the behavior is here?

A map lookup seems like it'd be expensive versus the logic on lines 400-408, am I thinking about the relative cost wrong? Why do a map lookup if the decl kind isn't a TU, and thus won't be inserted into the map?

The Insert below is inserting carbon_cpp_namespace, so it seems like there's nothing mapping to the decl_context being looked up here; what am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the call order here, can you document what the behavior is here?

Added this comment:

// If the context doesn't already have a mapping between C++ and Carbon,
// check if this is the root mapping (for the "Carbon" namespace in the
// translation unit scope) and if so, create that mapping.

in f84df3c but maybe it needs more than that? (see how the rest of the words in this comment go - if some of those would be worth adding to the code, or different ones)

A map lookup seems like it'd be expensive versus the logic on lines 400-408, am I thinking about the relative cost wrong? Why do a map lookup if the decl kind isn't a TU, and thus won't be inserted into the map?

You're probably right about the relative cost - though this seemed to me like a clearer way to express the intent of the code in terms of readability. Not sure though, perhaps there's a similarly readable alternative.

The idea is that we only map things into contexts we've previously mapped - except for the root/"Carbon" namespace (it's the start of the inductive reasoning).

The Insert below is inserting carbon_cpp_namespace, so it seems like there's nothing mapping to the decl_context being looked up here; what am I missing?

Let's say you've got some C++ naming Carbon::Foo::Bar.

Initially Clang asks whether Carbon knows about anything called "Carbon" in the global namespace, we say yes, construct a new NamespaceDecl and record our internal mapping from Package to "Carbon" namespace.

Next Clang comes back and asks if we know anything about a "Foo" in the Carbon NamespaceDecl. We look up in the map, see that the Carbon NamespaceDecl has an associated InstId, so we use that InstId to lookup "Foo" on the carbon side, find it's another namespace, create another mapping from the new "Foo" NamespaceDecl to the carbon InstId for that namespace. Repeat as needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're probably right about the relative cost - though this seemed to me like a clearer way to express the intent of the code in terms of readability. Not sure though, perhaps there's a similarly readable alternative.

What kinds of C++ name lookups would this occur in? That's really important for how heavily I'd suggest weighing performance. e.g. if somebody writes x (not namespace qualified), ideally that doesn't result in any map lookups here unless it's in some namespace Carbon { ... x ... }

Let's say you've got some C++ naming Carbon::Foo::Bar.

Thanks for this explanation, I was misunderstanding the code. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, one more connected thought -- if this is called frequently, then given an input C++ decl and a desire to avoid maps, I don't know if walking up parent contexts to see if Carbon is a parent would be faster than a map lookup on the whole. But that's kind of stray comment, maybe TODO to try territory

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name lookups we should get are for any scope we say could have external things - which is the global/TU scope, and the nested scopes we create. The nested ones we know will have a map lookup - but we can avoid the global one if we store whether we've already initialized the "Carbon" namespace (if we have, then any other query for any name in the global scope should fail - we don't have any other names to provide there)

Maybe 801785e is something roughly right for now?

Ultimately, yeah, we might end up doing a parent context walk (once we figure out how to handle Clang Modules support (that also uses an ExternalASTSource) along with Carbon - then we'd get more callbacks for other scopes that aren't the ones we care about (ones that have names in modules)) in the future, but not sure it's worth a TODO - could add one if you like.

dwblaikie added a commit to dwblaikie/carbon-lang that referenced this pull request Mar 23, 2026
This doesn't feel like the nicest way to do it (adding the check in 2
different places per lookup function) but since it has to wait until
after the insertion callback has constructed the key, with the current
structure I don't see a great alternative.

The return operation could be restructured into a function that DCHECKs
and then constructs the key value result type, and some of this code
could be commoned into an intermediate base (between BaseImpl and
MapBase/SetBase)?

This only tests one of the 6 checks introduced in this patch, because
death tests are especially expensive (partly innate, and partly due to
the cost of symbolizing failures - if we figure out how to disable the
symbolizer in death tests (llvm has this problem too/could benefit from
a solution) then maybe we could more comprehensively test these paths)

(the test could be made more explicit by using custom types that
intentionally hash differently - but that seems more verbose/perhaps
harder to follow that relying on the pointer conversion that was where
the problem was initially observed)

Discovered while working on carbon-language#6940
@dwblaikie dwblaikie requested review from jonmeow and zygoloid March 23, 2026 22:06
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, thanks for the comments!

auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;

private:
auto MapInstIdToClangDecl(clang::DeclContext& decl_context,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, maybe just "// See ExternalASTSource" -- I'd missed the override at the end

Comment on lines +396 to +397
auto decl_context_inst_id =
scope_mapping.Lookup(const_cast<clang::DeclContext*>(decl_context));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're probably right about the relative cost - though this seemed to me like a clearer way to express the intent of the code in terms of readability. Not sure though, perhaps there's a similarly readable alternative.

What kinds of C++ name lookups would this occur in? That's really important for how heavily I'd suggest weighing performance. e.g. if somebody writes x (not namespace qualified), ideally that doesn't result in any map lookups here unless it's in some namespace Carbon { ... x ... }

Let's say you've got some C++ naming Carbon::Foo::Bar.

Thanks for this explanation, I was misunderstanding the code. :)

dwblaikie and others added 4 commits March 23, 2026 16:44
Copy link
Contributor

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments have been addressed; I'm happy whenever jonmeow is. (I see that dwblaikie rerequested review from Jon so I'm not just merging.)

Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG, the CHECK on decl_context_inst_id especially helps me.

@jonmeow jonmeow added this pull request to the merge queue Mar 24, 2026
Merged via the queue into carbon-language:trunk with commit 2af5f97 Mar 24, 2026
8 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Mar 25, 2026
Generate class declarations for Carbon classes referenced from C++

Based on #6940, review from eb62070
onwards
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants