From 735353a597431833f08c976e80ae01742516b1ba Mon Sep 17 00:00:00 2001 From: stepan Date: Fri, 15 May 2026 15:07:47 +0200 Subject: [PATCH] Fix __new__ lookup for custom metatypes. --- .../src/tests/test_class.py | 54 +++++++++++++++++-- .../objects/type/slots/TpSlotVarargs.java | 22 ++++++-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_class.py b/graalpython/com.oracle.graal.python.test/src/tests/test_class.py index bfd53b0398..e950478bfe 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_class.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_class.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 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 @@ -117,10 +117,54 @@ class notAMeta(metaclass=Meta): pass notAMeta2 = type("notAMeta2", (notAMeta,), {}) - # the below assertions should pass, but this is such an unusual case that we - # ignore this. - # assert notAMeta.metatype is NewDescriptor - # assert notAMeta2.metatype is NewDescriptor + assert notAMeta.metatype is NewDescriptor + assert notAMeta2.metatype is NewDescriptor + + +def test_meta_new_default_metatype(): + class Meta(type): + def __new__(*args, **kwargs): + cls = type.__new__(*args, **kwargs) + cls.metatype = "meta" + return cls + + class uses_meta(metaclass=Meta): + pass + + uses_meta2 = type("uses_meta2", (uses_meta,), {}) + + assert uses_meta.metatype == "meta" + assert uses_meta2.metatype == "meta" + + +def test_meta_meta_new_custom_getattribute(): + meta_new_calls = [] + + class MetaMeta(type): + def __getattribute__(self, name): + if name == "__new__": + def new(*args, **kwargs): + cls = type.__new__(*args, **kwargs) + cls.metatype = "getattribute" + return cls + return new + return super().__getattribute__(name) + + class Meta(type, metaclass=MetaMeta): + def __new__(*args, **kwargs): + meta_new_calls.append(True) + cls = type.__new__(*args, **kwargs) + cls.metatype = "meta" + return cls + + class uses_getattribute(metaclass=Meta): + pass + + uses_getattribute2 = type("uses_getattribute2", (uses_getattribute,), {}) + + assert uses_getattribute.metatype == "getattribute" + assert uses_getattribute2.metatype == "getattribute" + assert meta_new_calls == [] def test_subclasses_collection(): diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotVarargs.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotVarargs.java index 43f64bfb6e..fd7a8d2ac1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotVarargs.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/slots/TpSlotVarargs.java @@ -82,6 +82,7 @@ import com.oracle.graal.python.builtins.objects.type.slots.TpSlotVarargsFactory.CallSlotTpCallNodeGen; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotVarargsFactory.CallSlotTpInitNodeGen; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotVarargsFactory.CallSlotTpNewNodeGen; +import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; @@ -97,6 +98,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; 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.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.PythonContext.GetThreadStateNode; @@ -110,6 +112,7 @@ import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Exclusive; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -484,14 +487,27 @@ public static Object executeUncached(TpSlot slot, Object self, Object[] args, PK return CallSlotTpNewNodeGen.getUncached().execute(null, null, slot, self, args, keywords); } - @Specialization - static Object callPython(VirtualFrame frame, Node inliningTarget, TpSlotPythonSingle slot, Object self, Object[] args, PKeyword[] keywords, + @Specialization(guards = "hasDefaultMetatype(inliningTarget, self, getClassNode)", limit = "1") + static Object callPythonFast(VirtualFrame frame, Node inliningTarget, TpSlotPythonSingle slot, Object self, Object[] args, PKeyword[] keywords, + @Cached GetClassNode getClassNode, @Cached BindNewMethodNode bindNew, - @Cached(inline = false) CallNode callNode) { + @Exclusive @Cached(inline = false) CallNode callNode) { Object callable = bindNew.execute(frame, inliningTarget, slot.getCallable(), self); return callNode.execute(frame, callable, PythonUtils.prependArgument(self, args), keywords); } + @Specialization(replaces = "callPythonFast") + static Object callPython(VirtualFrame frame, Node inliningTarget, @SuppressWarnings("unused") TpSlotPythonSingle slot, Object self, Object[] args, PKeyword[] keywords, + @Cached PyObjectGetAttr getNew, + @Exclusive @Cached(inline = false) CallNode callNode) { + Object callable = getNew.execute(frame, inliningTarget, self, T___NEW__); + return callNode.execute(frame, callable, PythonUtils.prependArgument(self, args), keywords); + } + + static boolean hasDefaultMetatype(Node inliningTarget, Object self, GetClassNode getClassNode) { + return getClassNode.execute(inliningTarget, self) == PythonBuiltinClassType.PythonClass; + } + @Specialization @InliningCutoff static Object callNative(VirtualFrame frame, TpSlotCExtNative slot, Object self, Object[] args, PKeyword[] keywords,