Skip to content

Preserve using-declaration access for inherited methods#995

Open
guitargeek wants to merge 1 commit into
compiler-research:mainfrom
guitargeek:cppyy-220
Open

Preserve using-declaration access for inherited methods#995
guitargeek wants to merge 1 commit into
compiler-research:mainfrom
guitargeek:cppyy-220

Conversation

@guitargeek
Copy link
Copy Markdown

When GetClassDecls<CXXMethodDecl> walked a class's decls and hit a UsingShadowDecl whose target is a method, it pushed the target CXXMethodDecl from the base class. The target's getAccess() returns the access in the base (e.g. protected); the elevated access carried by the using-declaration (public in the introducing class) was silently lost. Consumers like CPyCppyy filter on IsPublicMethod, so a republished overload such as

class Base   { protected: void foo(int, int); };
class Derived: public Base {
public:
    using Base::foo;
    void foo(int);
};

would never reach the Python proxy - calling derived.foo(1, 2) from cppyy raised TypeError: takes at most 1 arguments (2 given) (cppyy issue compiler-research/cppyy#220).

Push the UsingShadowDecl itself (for the non-constructor case; constructor-using-shadows still go through findInheritingConstructor) and teach CheckMethodAccess to consult USD->getAccess(). A small UnwrapUsingShadowToFunction helper unwraps the shadow at the entry of every API that downcasts a TCppFunction_t to FunctionDecl (return type, arg counts/types/names/defaults, signature, IsConst/Static/Virtual/Constructor/Destructor/Templated/Explicit, function address, operator arity).

MakeFunctionCallable keeps the unwrapped target as the wrapped function but threads a relaxAccessControl flag into make_wrapper when the caller passed a using-shadow: the generated wrapper still references the target by its original qualified name (e.g. ((Base*)obj)->Base::foo(...)), so access control has to be relaxed during wrapper compilation for it to compile. The runtime call is well-defined because the using-declaration made the member reachable through the derived class.

Adds a FunctionReflection_GetClassMethods_UsingShadowAccess regression test covering both the public-promoted case from the issue and a protected-using control case (which must stay hidden).

Fixes: compiler-research/cppyy#220

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 97.72727% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 82.83%. Comparing base (1ea28e2) to head (03c9439).

Files with missing lines Patch % Lines
lib/CppInterOp/CppInterOp.cpp 97.72% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #995      +/-   ##
==========================================
+ Coverage   82.78%   82.83%   +0.05%     
==========================================
  Files          15       15              
  Lines        5048     5069      +21     
==========================================
+ Hits         4179     4199      +20     
- Misses        869      870       +1     
Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.00% <97.72%> (+0.04%) ⬆️
Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.00% <97.72%> (+0.04%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

When `GetClassDecls<CXXMethodDecl>` walked a class's decls and hit a
`UsingShadowDecl` whose target is a method, it pushed the *target*
`CXXMethodDecl` from the base class. The target's `getAccess()` returns
the access in the base (e.g. `protected`); the elevated access carried
by the using-declaration (`public` in the introducing class) was
silently lost. Consumers like CPyCppyy filter on `IsPublicMethod`, so a
republished overload such as

    class Base   { protected: void foo(int, int); };
    class Derived: public Base {
    public:
        using Base::foo;
        void foo(int);
    };

would never reach the Python proxy - calling `derived.foo(1, 2)` from
cppyy raised `TypeError: takes at most 1 arguments (2 given)`
(cppyy issue compiler-research/cppyy#220).

Push the `UsingShadowDecl` itself (for the non-constructor case;
constructor-using-shadows still go through `findInheritingConstructor`)
and teach `CheckMethodAccess` to consult `USD->getAccess()`. A small
`UnwrapUsingShadowToFunction` helper unwraps the shadow at the entry
of every API that downcasts a `TCppFunction_t` to `FunctionDecl`
(return type, arg counts/types/names/defaults, signature,
IsConst/Static/Virtual/Constructor/Destructor/Templated/Explicit,
function address, operator arity).

`MakeFunctionCallable` keeps the unwrapped target as the wrapped
function but threads a `relaxAccessControl` flag into `make_wrapper`
when the caller passed a using-shadow: the generated wrapper still
references the target by its original qualified name (e.g.
`((Base*)obj)->Base::foo(...)`), so access control has to be relaxed
during wrapper compilation for it to compile. The runtime call is
well-defined because the using-declaration made the member reachable
through the derived class.

Adds a `FunctionReflection_GetClassMethods_UsingShadowAccess`
regression test covering both the public-promoted case from the issue
and a protected-using control case (which must stay hidden).

Fixes: compiler-research/cppyy#220
if (auto* USD = llvm::dyn_cast_or_null<UsingShadowDecl>(D)) {
if (llvm::isa_and_nonnull<CXXMethodDecl>(USD->getTargetDecl()))
return USD->getAccess() == AS;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not using UnwrapUsingShadowToFunction?

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.

Regression in using protected base class members as public methods in derived classes

2 participants