Skip to content

[cxx-interop] Use formal C++ interop mode to fix name lookup in module interfaces #79984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 14, 2025

Conversation

j-hui
Copy link
Contributor

@j-hui j-hui commented Mar 13, 2025

It is possible for a module interface (e.g., ModuleA) to be generated
with C++ interop disabled, and then rebuilt with C++ interop enabled
(e.g., because ModuleB, which imports ModuleA, has C++ interop enabled).

This circumstance can lead to various issues when name lookup behaves
differently depending on whether C++ interop is enabled, e.g., when
a module name is shadowed by a namespace of the same name---this only
happens in C++ because namespaces do not exist in C. Unfortunately,
naming namespaces the same as a module is a common C++ convention,
leading to many textual interfaces whose fully-qualified identifiers
(e.g., c_module.c_member) cannot be correctly resolved when C++ interop
is enabled (because c_module is shadowed by a namespace of the same
name).

This patch does two things. First, it introduces a new frontend flag,
-formal-cxx-interoperability-mode, which records the C++ interop mode
a module interface was originally compiled with. Doing so allows
subsequent consumers of that interface to interpret it according to the
formal C++ interop mode. Note that the actual "versioning" used by this
flag is very crude: "off" means disabled, and "swift-6" means enabled.
This is done to be compatible with C++ interop compat versioning scheme,
which seems to produce some invalid (but unused) version numbers. The
versioning scheme for both the formal and actual C++ interop modes
should be clarified and fixed in a subsequent patch.

The second thing this patch does is fix the module/namespace collision
issue in module interface files. It uses the formal C++ interop mode to
determine whether it should resolve C++-only decls during name lookup.
For now, the fix is very minimal and conservative: it only filters out
C++ namespaces during unqualified name lookup in an interface that was
originally generated without C++ interop. Doing so should fix the issue
while minimizing the chance for collateral breakge. More cases other
than C++ namespaces should be added in subsequent patches, with
sufficient testing and careful consideration.

rdar://144566922

@j-hui
Copy link
Contributor Author

j-hui commented Mar 13, 2025

@swift-ci please test

…e interfaces

It is possible for a module interface (e.g., ModuleA) to be generated
with C++ interop disabled, and then rebuilt with C++ interop enabled
(e.g., because ModuleB, which imports ModuleA, has C++ interop enabled).

This circumstance can lead to various issues when name lookup behaves
differently depending on whether C++ interop is enabled, e.g., when
a module name is shadowed by a namespace of the same name---this only
happens in C++ because namespaces do not exist in C. Unfortunately,
naming namespaces the same as a module is a common C++ convention,
leading to many textual interfaces whose fully-qualified identifiers
(e.g., c_module.c_member) cannot be correctly resolved when C++ interop
is enabled (because c_module is shadowed by a namespace of the same
name).

This patch does two things. First, it introduces a new frontend flag,
-formal-cxx-interoperability-mode, which records the C++ interop mode
a module interface was originally compiled with. Doing so allows
subsequent consumers of that interface to interpret it according to the
formal C++ interop mode. Note that the actual "versioning" used by this
flag is very crude: "off" means disabled, and "swift-6" means enabled.
This is done to be compatible with C++ interop compat versioning scheme,
which seems to produce some invalid (but unused) version numbers. The
versioning scheme for both the formal and actual C++ interop modes
should be clarified and fixed in a subsequent patch.

The second thing this patch does is fix the module/namespace collision
issue in module interface files. It uses the formal C++ interop mode to
determine whether it should resolve C++-only decls during name lookup.
For now, the fix is very minimal and conservative: it only filters out
C++ namespaces during unqualified name lookup in an interface that was
originally generated without C++ interop. Doing so should fix the issue
while minimizing the chance for collateral breakge. More cases other
than C++ namespaces should be added in subsequent patches, with
sufficient testing and careful consideration.

rdar://144566922
@j-hui j-hui force-pushed the fix-module-namespace-ambiguity branch from 3622f80 to 81cc9f9 Compare March 13, 2025 06:50
@j-hui
Copy link
Contributor Author

j-hui commented Mar 13, 2025

@swift-ci Please test

@@ -573,7 +573,7 @@ ERROR(dont_enable_interop_and_compat,none,
"'-cxx-interoperability-mode'; remove '-enable-experimental-cxx-interop'", ())

NOTE(valid_cxx_interop_modes,none,
"valid arguments to '-cxx-interoperability-mode=' are %0", (StringRef))
"valid arguments to '%0' are %1", (StringRef, StringRef))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If we only have 2 options here, maybe a %select{ would be a better way to take the arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My argument in favor of taking a StringRef here is that, at the call site, arg->getSpelling() means something at face-value, rather than true/false or 1/0. But I don't feel very strongly about this. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Orthogonally I'm even wondering whether we should combine this with this other note:

NOTE(note_valid_swift_versions, none,
     "valid arguments to '-swift-version' are %0", (StringRef))

In fact, valid arguments to <flag> are <options> seems like a very re-usable note and it would be nice to de-duplicate some of that.

But I think that can also be done in a follow-up patch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. I am fine with this as is then.

// non-trivial structs, and scoped enums; but it is not obvious for
// other kinds of decls, e.g., an enum member or some variable.
//
// TODO: enumerate those kinds in a precise and robust way
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we could ever be fully precise just by looking at the AST. People might have ifdefs in their code and having arbitrary pieces of code only present in some language modes. Hopefully, most libraries would be well behaved and would not do this. But it would be nice if we had a diagnostic in the future where at least we could tell our users that this was going on (if we can detect this).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this comment is mostly referring to other clearly C++ decls like template functions and using decls.

As you point out, it's impossible to reliably warn users either because of ifdefs.

@j-hui
Copy link
Contributor Author

j-hui commented Mar 13, 2025

@swift-ci please test

@j-hui
Copy link
Contributor Author

j-hui commented Mar 13, 2025

@swift-ci please test

@j-hui j-hui enabled auto-merge (squash) March 13, 2025 23:11
@j-hui j-hui merged commit 76a1742 into swiftlang:main Mar 14, 2025
5 checks passed
@j-hui j-hui deleted the fix-module-namespace-ambiguity branch March 18, 2025 17:28
@j-hui
Copy link
Contributor Author

j-hui commented Mar 29, 2025

Some book-keeping: this patch addresses the specific instance of the issue described in #56573, where a C++ namespace collides with the name of another module.

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.

3 participants