Skip to content

Conversation

@pfeodrippe
Copy link

@pfeodrippe pfeodrippe commented Nov 24, 2025

Fixes #164885.

Used Copilot. Run all clang tests locally with ninja check-clang (everything that was gree in main is also green here.)

From #164885, clang-repl fails to handle private member aliases in member function return type, we can see the issue below in clang-repl that works just fine in normal clang.

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.

/***** 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; }
# ***** 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.

# LLVM 22 (head)

@github-actions
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@pfeodrippe pfeodrippe changed the title clang][Parser] Allow private type aliases in out-of-line member function return types [clang][Parser] Allow private type aliases in out-of-line member function return types Nov 24, 2025
@pfeodrippe pfeodrippe force-pushed the private-member-aliases branch 3 times, most recently from 508717f to 58432c2 Compare November 25, 2025 01:03
…tion return types

When parsing qualified type names (e.g., `io_context::impl_type`) at file scope in
clang-repl, suppress access checks during type annotation. This allows private member
type aliases to be used in return types of out-of-line member function definitions,
matching the C++ standard's scoping rules for such declarations.

Fixes: Parsing errors in clang-repl when including headers with out-of-line member
functions that return private nested types (e.g., ASIO's io_context::impl_type).
@pfeodrippe pfeodrippe force-pushed the private-member-aliases branch from 58432c2 to 7563ce7 Compare November 25, 2025 01:22
@jeaye
Copy link
Contributor

jeaye commented Nov 25, 2025

@vgvassilev Would you mind please reviewing this for viability? It's a very small change, but I can't be sure it's the correct way to go about this. We're aiming to solve this problem #164885 which currently prevents clang::Interpreter from using boost.asio.

@pfeodrippe pfeodrippe marked this pull request as ready for review November 26, 2025 02:58
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 26, 2025

@llvm/pr-subscribers-clang

Author: Paulo Rafael Feodrippe (pfeodrippe)

Changes

Fixes #164885.

Used Copilot. Run all clang tests locally with ninja check-clang (everything that was gree in main is also green here.)

From #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.

/***** 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; }
/***** in clang-repl *****/clang-repl
clang-repl&gt; #include "asio-repro.hpp"
In file included from &lt;&lt;&lt; inputs &gt;&gt;&gt;: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 --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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang-repl fails to handle private member aliases in member function return types

3 participants