[clang] [clang][Parser] Allow private type aliases in out-of-line member function return types (PR #169272)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 25 18:59:02 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Paulo Rafael Feodrippe (pfeodrippe)
<details>
<summary>Changes</summary>
Fixes https://github.com/llvm/llvm-project/issues/164885.
Used Copilot. Run all clang tests locally with `ninja check-clang` (everything that was gree in main is also green here.)
>From https://github.com/llvm/llvm-project/issues/164885, we can see the issue below in clang-repl that works just fine in normal clang. The issue happens when we are parsing input like "A::B *A::foo()" where the type "A::B" might be a private member type, but if this turns out to be an out-of-line member function definition.
The fix, only applied in incremental mode (clang-repl / Interpreter) seems to be to suppress access checks for qualified type lookups when no delayed diagnostic pool is active.
```c
/***** somecode.c *****/
struct scheduler
{ };
class io_context
{
using impl_type = scheduler;
public:
impl_type *foo();
};
/* The error doesn't occur if `impl_type` is a parameter type. Only for the return type. */
io_context::impl_type *io_context::foo()
{ return nullptr; }
```
```c
/***** in clang-repl *****/
❯ clang-repl
clang-repl> #include "asio-repro.hpp"
In file included from <<< inputs >>>:1:
In file included from input_line_1:1:
./asio-repro.hpp:13:13: error: 'impl_type' is a private member of 'io_context'
13 | io_context::impl_type *io_context::add_impl()
| ^
./asio-repro.hpp:7:9: note: implicitly declared private here
7 | using impl_type = scheduler;
| ^
error: Parsing failed.
```
---
Full diff: https://github.com/llvm/llvm-project/pull/169272.diff
2 Files Affected:
- (modified) clang/lib/Parse/Parser.cpp (+21)
- (added) clang/test/Interpreter/private-member-access.cpp (+60)
``````````diff
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index a6fc676f23a51..ac5c90bf7c7ac 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -2020,6 +2020,27 @@ bool Parser::TryAnnotateTypeOrScopeTokenAfterScopeSpec(
CXXScopeSpec &SS, bool IsNewScope,
ImplicitTypenameContext AllowImplicitTypename) {
if (Tok.is(tok::identifier)) {
+ // In incremental/clang-repl mode, suppress access checks for qualified
+ // type lookups when no delayed diagnostic pool is active. This handles
+ // the case where we're parsing input like "A::B *A::foo()" where the type
+ // "A::B" might be a private member type, but if this turns out to be an
+ // out-of-line member function definition, access should be allowed.
+ //
+ // When the declaration is actually parsed (via ParseDeclarationOrFunctionDefinition),
+ // the ParsingDeclSpec will set up proper delayed diagnostics to handle
+ // access checking in the correct context.
+ //
+ // We only do this in incremental mode because this is where the issue
+ // manifests - disambiguation happens before ParsingDeclSpec is created.
+ // In normal compilation, these access checks would be re-triggered during
+ // actual parsing with delayed diagnostics active.
+ bool SuppressAccess = getLangOpts().IncrementalExtensions &&
+ SS.isNotEmpty() &&
+ !Actions.DelayedDiagnostics.shouldDelayDiagnostics();
+ std::optional<SuppressAccessChecks> SAC;
+ if (SuppressAccess)
+ SAC.emplace(*this, true);
+
// Determine whether the identifier is a type name.
if (ParsedType Ty = Actions.getTypeName(
*Tok.getIdentifierInfo(), Tok.getLocation(), getCurScope(), &SS,
diff --git a/clang/test/Interpreter/private-member-access.cpp b/clang/test/Interpreter/private-member-access.cpp
new file mode 100644
index 0000000000000..a18860e8ab152
--- /dev/null
+++ b/clang/test/Interpreter/private-member-access.cpp
@@ -0,0 +1,60 @@
+// RUN: cat %s | clang-repl | FileCheck %s
+
+extern "C" int printf(const char*, ...);
+
+// Test 1: Private nested struct in return type
+class Container { struct Node { int data; }; public: Node* create(); };
+Container::Node* Container::create() { return new Node{456}; }
+printf("Private nested struct: %d\n", Container().create()->data);
+// CHECK: Private nested struct: 456
+
+// Test 2: Private enum in return type
+class Status { enum Code { OK = 0, ERROR = 1 }; public: Code get(); };
+Status::Code Status::get() { return OK; }
+printf("Private enum: %d\n", Status().get());
+// CHECK: Private enum: 0
+
+// Test 3: Template with private type alias
+template<typename T> class Handler { using ptr = T*; public: ptr get(); };
+template<typename T> typename Handler<T>::ptr Handler<T>::get() { return nullptr; }
+printf("Template with private type: passed\n");
+// CHECK: Template with private type: passed
+
+// Test 4: Protected type alias (not just private)
+class ProtectedBase { protected: using value_type = int; public: value_type get(); };
+ProtectedBase::value_type ProtectedBase::get() { return 42; }
+printf("Protected type alias: %d\n", ProtectedBase().get());
+// CHECK: Protected type alias: 42
+
+// Test 5: Deeply nested private type (A::B::C)
+class Outer { public: class Middle { struct Inner { int x; }; public: Inner* create(); }; };
+Outer::Middle::Inner* Outer::Middle::create() { return new Inner{789}; }
+printf("Deeply nested: %d\n", Outer::Middle().create()->x);
+// CHECK: Deeply nested: 789
+
+// Test 6: Private typedef (not using declaration)
+class WithTypedef { typedef double real_t; public: real_t compute(); };
+WithTypedef::real_t WithTypedef::compute() { return 2.718; }
+printf("Private typedef: %.3f\n", WithTypedef().compute());
+// CHECK: Private typedef: 2.718
+
+// Test 7: Const-qualified return type with private type
+class ConstReturn { using data_t = int; public: const data_t& get(); private: data_t val = 100; };
+const ConstReturn::data_t& ConstReturn::get() { return val; }
+printf("Const return: %d\n", ConstReturn().get());
+// CHECK: Const return: 100
+
+// Test 8: Reference return type with private type
+class RefReturn { using ref_t = int; ref_t value = 55; public: ref_t& getRef(); };
+RefReturn::ref_t& RefReturn::getRef() { return value; }
+RefReturn rr; rr.getRef() = 66;
+printf("Reference return: %d\n", rr.getRef());
+// CHECK: Reference return: 66
+
+// Test 9: Pointer-to-pointer with private type
+class PtrPtr { using inner_t = int; public: inner_t** get(); };
+PtrPtr::inner_t** PtrPtr::get() { static int* p = nullptr; return &p; }
+printf("Pointer to pointer: passed\n");
+// CHECK: Pointer to pointer: passed
+
+%quit
``````````
</details>
https://github.com/llvm/llvm-project/pull/169272
More information about the cfe-commits
mailing list