Skip to content

[clang] [Sema] Preserve nested name specifier prefix in MemberPointerType #118236

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 1 commit into from
Dec 8, 2024

Conversation

HighCommander4
Copy link
Collaborator

Fixes #118198

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra clangd clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 2, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 2, 2024

@llvm/pr-subscribers-clangd
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang

Author: Nathan Ridge (HighCommander4)

Changes

Fixes #118198


Full diff: https://github.com/llvm/llvm-project/pull/118236.diff

2 Files Affected:

  • (modified) clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp (+7)
  • (modified) clang/lib/Sema/SemaType.cpp (+10-7)
diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
index 30b9b1902aa9c7..1ec51d862d0a6f 100644
--- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
@@ -1092,6 +1092,13 @@ sizeof...($TemplateParameter[[Elements]]);
             $Field_dependentName[[waldo]];
           }
         };
+    )cpp",
+      // Pointer-to-member with nested-name-specifiers
+      R"cpp(
+      struct $Class_def[[Outer]] {
+        struct $Class_def[[Inner]] {};
+      };
+      using $Typedef_decl[[Alias]] = void ($Class[[Outer]]::$Class[[Inner]]:: *)();
     )cpp"};
   for (const auto &TestCase : TestCases)
     // Mask off scope modifiers to keep the tests manageable.
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index f32edc5ac06440..73c9362208b14b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -5347,15 +5347,18 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
 
         case NestedNameSpecifier::TypeSpec:
         case NestedNameSpecifier::TypeSpecWithTemplate:
-          ClsType = QualType(NNS->getAsType(), 0);
-          // Note: if the NNS has a prefix and ClsType is a nondependent
-          // TemplateSpecializationType, then the NNS prefix is NOT included
-          // in ClsType; hence we wrap ClsType into an ElaboratedType.
-          // NOTE: in particular, no wrap occurs if ClsType already is an
-          // Elaborated, DependentName, or DependentTemplateSpecialization.
-          if (isa<TemplateSpecializationType>(NNS->getAsType()))
+          const Type *NNSType = NNS->getAsType();
+          ClsType = QualType(NNSType, 0);
+          // If ClsType is an Elaborated, DependentName, or
+          // DependentTemplateSpecialization, it already stores the NNS prefix.
+          // Otherwise, wrap it in an Elaborated type to have a place to store
+          // the NNS prefix.
+          if (!(isa<ElaboratedType>(NNSType) ||
+                isa<DependentNameType>(NNSType) ||
+                isa<DependentTemplateSpecializationType>(NNSType))) {
             ClsType = Context.getElaboratedType(ElaboratedTypeKeyword::None,
                                                 NNSPrefix, ClsType);
+          }
           break;
         }
       } else {

@HighCommander4 HighCommander4 marked this pull request as draft December 2, 2024 01:28
@HighCommander4
Copy link
Collaborator Author

(Marked as Draft since there are test failures I need to investigate.)

Comment on lines 5356 to 5358
if (!(isa<ElaboratedType>(NNSType) ||
isa<DependentNameType>(NNSType) ||
isa<DependentTemplateSpecializationType>(NNSType))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be testing for conditions which seem impossible, the NNS would be malformed otherwise.

For example, what would it mean for the NNS type to be an ElaboratedType? If the NNS has a prefix, which would take precedence, that prefix or the ElaboratedType's prefix? If there is no prefix, what sense would it convey beyond using the Elaborated prefix as its prefix instead?

For DependentNameType, that would be redundant with how we represent these as Identifier + prefix, so we should prefer that over forming a NNS with such type.

The DTST is an exception here, as we lack a NNS kind for an identifier + list of template arguments, but then such DTST should not store a NNS itself, otherwise again this would be a bug somewhere else, as that would be redundant / confusing with the NNS' own prefix as explained above.

So if we are getting either an ElaboratedType or a DependentNameType here, there seems to be a bug elsewhere.

Regarding the explanation for DependentTemplateSpecialization: even if the type node itself can have a nested name, it should not be present when the node is used as the type for a NNS node itself.

We should handle a DTST here by rebuilding it, adding the NNSPrefix as the DTST's nested name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@mizvekov Thanks for having a look!

I don't have any evidence of us actually getting an ElaboratedType or DependentNameType here, I was just basing this on the comment which mentioned these types.

I revised the patch to make a more targeted change (wrap RecordType when there is a qualifier present), which still addresses the original issue I was having in clangd, and (hopefully) passes tests. I would be grateful of your opinion about whether there is a more appropriate condition we should be using.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I think the existing comment is wrong / misleading.

// already is an Elaborated, DependentName, or
// DependentTemplateSpecialization.
if (isa<TemplateSpecializationType>(NNSType) ||
(NNSPrefix && isa<RecordType>(NNSType)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(NNSPrefix && isa<RecordType>(NNSType)))
isa<RecordType>(NNSType))

We use an ElaboratedType with null prefix to represent a type which was written without qualifiers, so it seems arbitrary to not add the elaboration in this case.

Also, can you at least add a FIXME / TODO explaining how a DependentTemplateSpecializationType needs to be handled here, as I explained in the other thread?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would also be cool to have an assertion here, that we are handling all cases.

I think the logic here would be something like:

if (isa<DependentTemplateSpecializationType>...)
  // Rebuild DependentTemplateSpecializationType, adding the Prefix.
else
  // either the dependent case (TemplateSpecializationType), or the non-dependent one (RecordType).
 // assert(isa<TemplateSpecializationType, RecordType>...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We use an ElaboratedType with null prefix to represent a type which was written without qualifiers, so it seems arbitrary to not add the elaboration in this case.

Wrapping RecordTypes into ElaboratedTypes with a null prefix was causing a bunch of failures on the first draft of the patch.

For example, on this line:

int (Derived::*d) = data_ptr; // expected-error {{cannot cast private base class 'Base' to 'test1::Derived'}}

the change has the effect of changing the way the derived type is printed from test1::Derived to just Derived, I guess because when the diagnostic formatter encounters an ElaboratedType it says "I know how the type was printed in the source, I will print it the same way in the diagnostic".

Should we accept changes like this to diagnostic output?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes exactly, this test change is correct, because the type was written as 'Derived' and not 'test1::Derived', and the diagnostics are supposed to print the type as written. It would be inconsistent to print the type as written when it has any qualifier, but print it fully qualified when it does not have any.

We started doing this consistently since this patch: https://reviews.llvm.org/D112374

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would also be cool to have an assertion here, that we are handling all cases.

I think the logic here would be something like:

if (isa<DependentTemplateSpecializationType>...)
  // Rebuild DependentTemplateSpecializationType, adding the Prefix.
else
  // either the dependent case (TemplateSpecializationType), or the non-dependent one (RecordType).
 // assert(isa<TemplateSpecializationType, RecordType>...)

I tried this, but SemaCXX/err_init_conversion_failed.cpp trips the assertion; we get a TemplateTypeParmType here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, well this is one case I left out :) The assert is still viable and would be helpful, but it's not critical for merging this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The assert is still viable and would be helpful

FWIW, I did try adding back the assert to see what other sorts of types we get in this codepath. I found the following:

  • TemplateTypeParmType (seen in SemaCXX/err_init_conversion_failed.cpp)
  • TypedefType (seen in CXX/drs/cwg3xx.cpp)
  • DecltypeType (seen in CXX/expr/expr_prim/expr_prim_general/p8-0x.cpp)
  • InjectedClassNameType (seen in CXX/temp/temp_decls/temp_variadic/p5.cpp)
  • UsingType (seen in Modules/using-decl.cpp)

It's not clear to me what sort of handling we'd want for these various types.

@HighCommander4
Copy link
Collaborator Author

I updated the patch to do the wrapping into ElaboratedType even if a prefix isn't present, and updated the various failing tests accordingly.

I haven't added an assert, as explained in the previous comment.

@HighCommander4
Copy link
Collaborator Author

Fixed one additional test failure.

@HighCommander4
Copy link
Collaborator Author

CI is green. Requesting review.

@HighCommander4 HighCommander4 marked this pull request as ready for review December 6, 2024 04:05
Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM!

@HighCommander4 HighCommander4 merged commit 70c1764 into llvm:main Dec 8, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category clang-tools-extra clangd
Projects
None yet
Development

Successfully merging this pull request may close these issues.

No TypeLoc node for outer nested-name-specifier in pointer-to-member type
3 participants