From 1b32f7c3e326c3b155fdad46e6fe8fb9859df612 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 23 Mar 2026 10:31:25 +0100 Subject: [PATCH] Fix to use overridden `__qualname__` when that is set on a method's function --- .../src/tests/test_methods.py | 20 ++- .../method/AbstractMethodBuiltins.java | 119 ++++-------------- 2 files changed, 46 insertions(+), 93 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_methods.py b/graalpython/com.oracle.graal.python.test/src/tests/test_methods.py index 7cda8a38f0..c27e3779a8 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_methods.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_methods.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0 @@ -89,3 +89,21 @@ def __call__(self, x, y): assert isinstance(method1(), A) assert method2(1) == "A is 1", method2(1) + + +def test_method_qualname_uses_wrapped_callable(): + import types + + def f(): + pass + + class A: + def g(self): + pass + + m = classmethod(f) + + assert A().g.__qualname__ == "test_method_qualname_uses_wrapped_callable..A.g" + assert types.MethodType(f, A).__qualname__ == "test_method_qualname_uses_wrapped_callable..f" + assert A.m.__qualname__ == "test_method_qualname_uses_wrapped_callable..f" + assert A.__dict__["m"].__qualname__ == "test_method_qualname_uses_wrapped_callable..f" diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/method/AbstractMethodBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/method/AbstractMethodBuiltins.java index 9b7de8fadc..4bc2f8dc4b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/method/AbstractMethodBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/method/AbstractMethodBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. * Copyright (c) 2014, Regents of the University of California * * All rights reserved. @@ -55,10 +55,8 @@ import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.module.PythonModule; -import com.oracle.graal.python.builtins.objects.str.StringUtils.SimpleTruffleStringFormatNode; import com.oracle.graal.python.builtins.objects.tuple.PTuple; import com.oracle.graal.python.builtins.objects.type.TpSlots; -import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun.HashBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; import com.oracle.graal.python.lib.PyObjectGetAttr; @@ -76,7 +74,6 @@ import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; -import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CannotCastException; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; @@ -84,7 +81,6 @@ import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.util.PythonUtils; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; @@ -285,105 +281,44 @@ static Object getDoc(PBuiltinMethod self, } } + static TruffleString getFunctionAttr(VirtualFrame frame, Object self, Node inliningTarget, CastToTruffleStringNode toStringNode, PyObjectGetAttr getAttr, PRaiseNode raiseNode, + TruffleString attrName) { + Object function; + if (self instanceof PMethod method) { + function = method.getFunction(); + } else { + function = ((PBuiltinMethod) self).getFunction(); + } + try { + return toStringNode.execute(inliningTarget, getAttr.execute(frame, inliningTarget, function, attrName)); + } catch (CannotCastException cce) { + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.IS_NOT_A_UNICODE_OBJECT, attrName); + } + } + @Builtin(name = J___NAME__, minNumOfPositionalArgs = 1, isGetter = true) @GenerateNodeFactory public abstract static class NameNode extends PythonUnaryBuiltinNode { @Specialization - static Object getName(VirtualFrame frame, PBuiltinMethod method, - @Bind Node inliningTarget, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("getAttr") @Cached PyObjectGetAttr getAttr) { - try { - return toStringNode.execute(inliningTarget, getAttr.execute(frame, inliningTarget, method.getFunction(), T___NAME__)); - } catch (CannotCastException cce) { - throw CompilerDirectives.shouldNotReachHere(); - } - } - - @Specialization - static Object getName(VirtualFrame frame, PMethod method, + static TruffleString getName(VirtualFrame frame, Object self, @Bind Node inliningTarget, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("getAttr") @Cached PyObjectGetAttr getAttr) { - try { - return toStringNode.execute(inliningTarget, getAttr.execute(frame, inliningTarget, method.getFunction(), T___NAME__)); - } catch (CannotCastException cce) { - throw CompilerDirectives.shouldNotReachHere(); - } + @Cached CastToTruffleStringNode toStringNode, + @Cached PyObjectGetAttr getAttr, + @Cached PRaiseNode raiseNode) { + return getFunctionAttr(frame, self, inliningTarget, toStringNode, getAttr, raiseNode, T___NAME__); } } @Builtin(name = J___QUALNAME__, minNumOfPositionalArgs = 1, isGetter = true) @GenerateNodeFactory public abstract static class QualNameNode extends PythonUnaryBuiltinNode { - - protected static boolean isSelfModuleOrNull(PMethod method) { - return method.getSelf() == PNone.NO_VALUE || PGuards.isPythonModule(method.getSelf()); - } - - protected static boolean isSelfModuleOrNull(PBuiltinMethod method) { - return method.getSelf() == PNone.NO_VALUE || PGuards.isPythonModule(method.getSelf()); - } - - @Specialization(guards = "isSelfModuleOrNull(method)") - static TruffleString doSelfIsModule(VirtualFrame frame, PMethod method, - @Bind Node inliningTarget, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("lookupName") @Cached PyObjectLookupAttr lookupName) { - return getName(frame, inliningTarget, method.getFunction(), toStringNode, lookupName); - } - - @Specialization(guards = "isSelfModuleOrNull(method)") - static TruffleString doSelfIsModule(VirtualFrame frame, PBuiltinMethod method, - @Bind Node inliningTarget, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("lookupName") @Cached PyObjectLookupAttr lookupName) { - return getName(frame, inliningTarget, method.getFunction(), toStringNode, lookupName); - } - - @Specialization(guards = "!isSelfModuleOrNull(method)") - static TruffleString doSelfIsObject(VirtualFrame frame, PMethod method, - @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Shared @Cached TypeNodes.IsTypeNode isTypeNode, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("getQualname") @Cached PyObjectGetAttr getQualname, - @Shared("lookupName") @Cached PyObjectLookupAttr lookupName, - @Shared("formatter") @Cached SimpleTruffleStringFormatNode simpleTruffleStringFormatNode, - @Shared @Cached PRaiseNode raiseNode) { - return getQualName(frame, inliningTarget, method.getSelf(), method.getFunction(), getClassNode, isTypeNode, toStringNode, getQualname, lookupName, simpleTruffleStringFormatNode, - raiseNode); - } - - @Specialization(guards = "!isSelfModuleOrNull(method)") - static TruffleString doSelfIsObject(VirtualFrame frame, PBuiltinMethod method, + @Specialization + static TruffleString getQualname(VirtualFrame frame, Object self, @Bind Node inliningTarget, - @Shared @Cached GetClassNode getClassNode, - @Shared @Cached TypeNodes.IsTypeNode isTypeNode, - @Shared("toStringNode") @Cached CastToTruffleStringNode toStringNode, - @Shared("getQualname") @Cached PyObjectGetAttr getQualname, - @Shared("lookupName") @Cached PyObjectLookupAttr lookupName, - @Shared("formatter") @Cached SimpleTruffleStringFormatNode simpleTruffleStringFormatNode, - @Shared @Cached PRaiseNode raiseNode) { - return getQualName(frame, inliningTarget, method.getSelf(), method.getFunction(), getClassNode, isTypeNode, toStringNode, getQualname, lookupName, simpleTruffleStringFormatNode, - raiseNode); - } - - private static TruffleString getQualName(VirtualFrame frame, Node inliningTarget, Object self, Object func, GetClassNode getClassNode, TypeNodes.IsTypeNode isTypeNode, - CastToTruffleStringNode toStringNode, PyObjectGetAttr getQualname, PyObjectLookupAttr lookupName, SimpleTruffleStringFormatNode simpleTruffleStringFormatNode, - PRaiseNode raiseNode) { - Object type = isTypeNode.execute(inliningTarget, self) ? self : getClassNode.execute(inliningTarget, self); - - try { - TruffleString typeQualName = toStringNode.execute(inliningTarget, getQualname.execute(frame, inliningTarget, type, T___QUALNAME__)); - return simpleTruffleStringFormatNode.format("%s.%s", typeQualName, getName(frame, inliningTarget, func, toStringNode, lookupName)); - } catch (CannotCastException cce) { - throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.IS_NOT_A_UNICODE_OBJECT, T___QUALNAME__); - } - } - - private static TruffleString getName(VirtualFrame frame, Node inliningTarget, Object func, CastToTruffleStringNode toStringNode, PyObjectLookupAttr lookupName) { - return toStringNode.execute(inliningTarget, lookupName.execute(frame, inliningTarget, func, T___NAME__)); + @Cached CastToTruffleStringNode toStringNode, + @Cached PyObjectGetAttr getQualname, + @Cached PRaiseNode raiseNode) { + return getFunctionAttr(frame, self, inliningTarget, toStringNode, getQualname, raiseNode, T___QUALNAME__); } }