Skip to content

Commit a0933eb

Browse files
committed
[GR-64592] Implement AttributeError builtins
PullRequest: graalpython/3776
2 parents 85c3fb0 + ca1a369 commit a0933eb

File tree

15 files changed

+302
-31
lines changed

15 files changed

+302
-31
lines changed

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_exceptions.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
test.test_exceptions.AttributeErrorTests.test_attribute_error_with_bad_name @ linux-x86_64
12
test.test_exceptions.AttributeErrorTests.test_attribute_error_with_failing_dict @ darwin-arm64,darwin-x86_64,linux-aarch64,linux-x86_64,win32-AMD64
3+
test.test_exceptions.AttributeErrorTests.test_attributes @ linux-x86_64
24
test.test_exceptions.AttributeErrorTests.test_getattr_error_bad_suggestions_do_not_trigger_for_small_names @ darwin-arm64,darwin-x86_64,linux-aarch64,linux-x86_64,win32-AMD64
5+
test.test_exceptions.AttributeErrorTests.test_getattr_has_name_and_obj @ linux-x86_64
6+
test.test_exceptions.AttributeErrorTests.test_getattr_has_name_and_obj_for_method @ linux-x86_64
37
test.test_exceptions.AttributeErrorTests.test_getattr_suggestions_do_not_trigger_for_big_dicts @ darwin-arm64,darwin-x86_64,linux-aarch64,linux-x86_64,win32-AMD64
48
test.test_exceptions.AttributeErrorTests.test_getattr_suggestions_do_not_trigger_for_long_attributes @ darwin-arm64,darwin-x86_64,linux-aarch64,linux-x86_64,win32-AMD64
59
test.test_exceptions.AttributeErrorTests.test_getattr_suggestions_for_same_name @ darwin-arm64,darwin-x86_64,linux-aarch64,linux-x86_64,win32-AMD64

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
import com.oracle.graal.python.builtins.objects.dict.PDict;
242242
import com.oracle.graal.python.builtins.objects.ellipsis.EllipsisBuiltins;
243243
import com.oracle.graal.python.builtins.objects.enumerate.EnumerateBuiltins;
244+
import com.oracle.graal.python.builtins.objects.exception.AttributeErrorBuiltins;
244245
import com.oracle.graal.python.builtins.objects.exception.BaseExceptionBuiltins;
245246
import com.oracle.graal.python.builtins.objects.exception.BaseExceptionGroupBuiltins;
246247
import com.oracle.graal.python.builtins.objects.exception.ImportErrorBuiltins;
@@ -570,6 +571,7 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) {
570571
new GenericAliasIteratorBuiltins(),
571572
new com.oracle.graal.python.builtins.objects.types.UnionTypeBuiltins(),
572573
// exceptions
574+
new AttributeErrorBuiltins(),
573575
new SystemExitBuiltins(),
574576
new ImportErrorBuiltins(),
575577
new StopIterationBuiltins(),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
import com.oracle.graal.python.builtins.objects.dict.DictViewBuiltins;
157157
import com.oracle.graal.python.builtins.objects.ellipsis.EllipsisBuiltins;
158158
import com.oracle.graal.python.builtins.objects.enumerate.EnumerateBuiltins;
159+
import com.oracle.graal.python.builtins.objects.exception.AttributeErrorBuiltins;
159160
import com.oracle.graal.python.builtins.objects.exception.BaseExceptionBuiltins;
160161
import com.oracle.graal.python.builtins.objects.exception.BaseExceptionGroupBuiltins;
161162
import com.oracle.graal.python.builtins.objects.exception.ImportErrorBuiltins;
@@ -683,7 +684,7 @@ It can be called either on the class (e.g. C.f()) or on an instance
683684
OverflowError("OverflowError", ArithmeticError, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
684685
ZeroDivisionError("ZeroDivisionError", ArithmeticError, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
685686
AssertionError("AssertionError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
686-
AttributeError("AttributeError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
687+
AttributeError("AttributeError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict().slots(AttributeErrorBuiltins.SLOTS)),
687688
BufferError("BufferError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
688689
EOFError("EOFError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict()),
689690
ImportError("ImportError", Exception, newBuilder().publishInModule(J_BUILTINS).basetype().addDict().slots(ImportErrorBuiltins.SLOTS)),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/TimeModuleBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ Object tzset() {
328328
}
329329
TimeZone.setDefault(TimeZone.getTimeZone(tzEnv));
330330
} else {
331-
PRaiseNode.raiseStatic(this, PythonBuiltinClassType.AttributeError, SET_TIMEZONE_ERROR);
331+
throw PRaiseNode.raiseStatic(this, PythonBuiltinClassType.AttributeError, SET_TIMEZONE_ERROR);
332332
}
333333
return PNone.NONE;
334334
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextDictBuiltins.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@
105105
import com.oracle.graal.python.lib.PyDictSetDefault;
106106
import com.oracle.graal.python.lib.PyObjectGetAttr;
107107
import com.oracle.graal.python.lib.PyObjectHashNode;
108-
import com.oracle.graal.python.lib.PyObjectLookupAttr;
109108
import com.oracle.graal.python.nodes.PRaiseNode;
110109
import com.oracle.graal.python.nodes.builtins.ListNodes.ConstructListNode;
111110
import com.oracle.graal.python.nodes.call.CallNode;
@@ -521,15 +520,15 @@ abstract static class PyDict_Merge extends CApiTernaryBuiltinNode {
521520
@Specialization(guards = {"override != 0"})
522521
static int merge(PDict a, Object b, @SuppressWarnings("unused") int override,
523522
@Bind("this") Node inliningTarget,
524-
@Cached PyObjectLookupAttr lookupKeys,
525-
@Cached PyObjectLookupAttr lookupAttr,
523+
@Shared @Cached PyObjectGetAttr getKeys,
524+
@Cached PyObjectGetAttr getUpdate,
526525
@Shared @Cached CallNode callNode,
527526
@Cached PRaiseNode raiseNode) {
528527
// lookup "keys" to raise the right error:
529-
if (lookupKeys.execute(null, inliningTarget, b, T_KEYS) == PNone.NO_VALUE) {
528+
if (getKeys.execute(null, inliningTarget, b, T_KEYS) == PNone.NO_VALUE) {
530529
throw raiseNode.raise(inliningTarget, AttributeError, OBJ_P_HAS_NO_ATTR_S, b, T_KEYS);
531530
}
532-
Object updateCallable = lookupAttr.execute(null, inliningTarget, a, T_UPDATE);
531+
Object updateCallable = getUpdate.execute(null, inliningTarget, a, T_UPDATE);
533532
callNode.executeWithoutFrame(updateCallable, new Object[]{b});
534533
return 0;
535534
}
@@ -561,7 +560,7 @@ static int merge(PDict a, PDict b, @SuppressWarnings("unused") int override,
561560
@Specialization(guards = {"override == 0", "!isDict(b)"})
562561
static int merge(PDict a, Object b, @SuppressWarnings("unused") int override,
563562
@Bind("this") Node inliningTarget,
564-
@Cached PyObjectGetAttr getAttrNode,
563+
@Shared @Cached PyObjectGetAttr getKeys,
565564
@Shared @Cached CallNode callNode,
566565
@Cached ConstructListNode listNode,
567566
@Cached GetItemNode getKeyNode,
@@ -570,7 +569,7 @@ static int merge(PDict a, Object b, @SuppressWarnings("unused") int override,
570569
@Cached HashingStorageSetItem setItemA,
571570
@Exclusive @Cached InlinedLoopConditionProfile loopProfile,
572571
@Cached InlinedBranchProfile noKeyProfile) {
573-
Object attr = getAttrNode.execute(null, inliningTarget, a, T_KEYS);
572+
Object attr = getKeys.execute(null, inliningTarget, a, T_KEYS);
574573
PList keys = listNode.execute(null, callNode.execute(null, attr));
575574

576575
SequenceStorage keysStorage = keys.getSequenceStorage();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.objects.exception;
42+
43+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___GETSTATE__;
44+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REDUCE__;
45+
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
46+
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
47+
48+
import java.util.List;
49+
50+
import com.oracle.graal.python.PythonLanguage;
51+
import com.oracle.graal.python.annotations.Slot;
52+
import com.oracle.graal.python.annotations.Slot.SlotKind;
53+
import com.oracle.graal.python.annotations.Slot.SlotSignature;
54+
import com.oracle.graal.python.builtins.Builtin;
55+
import com.oracle.graal.python.builtins.CoreFunctions;
56+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
57+
import com.oracle.graal.python.builtins.PythonBuiltins;
58+
import com.oracle.graal.python.builtins.objects.PNone;
59+
import com.oracle.graal.python.builtins.objects.common.EmptyStorage;
60+
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
61+
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes;
62+
import com.oracle.graal.python.builtins.objects.dict.PDict;
63+
import com.oracle.graal.python.builtins.objects.function.PKeyword;
64+
import com.oracle.graal.python.builtins.objects.type.TpSlots;
65+
import com.oracle.graal.python.nodes.ErrorMessages;
66+
import com.oracle.graal.python.nodes.PRaiseNode;
67+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
68+
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
69+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
70+
import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode;
71+
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles;
72+
import com.oracle.graal.python.nodes.object.GetClassNode;
73+
import com.oracle.graal.python.nodes.object.GetDictIfExistsNode;
74+
import com.oracle.graal.python.runtime.exception.PException;
75+
import com.oracle.graal.python.runtime.object.PFactory;
76+
import com.oracle.truffle.api.dsl.Bind;
77+
import com.oracle.truffle.api.dsl.Cached;
78+
import com.oracle.truffle.api.dsl.GenerateCached;
79+
import com.oracle.truffle.api.dsl.GenerateInline;
80+
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
81+
import com.oracle.truffle.api.dsl.GenerateUncached;
82+
import com.oracle.truffle.api.dsl.NodeFactory;
83+
import com.oracle.truffle.api.dsl.Specialization;
84+
import com.oracle.truffle.api.frame.VirtualFrame;
85+
import com.oracle.truffle.api.nodes.Node;
86+
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
87+
import com.oracle.truffle.api.profiles.InlinedLoopConditionProfile;
88+
import com.oracle.truffle.api.strings.TruffleString;
89+
90+
@CoreFunctions(extendClasses = PythonBuiltinClassType.AttributeError)
91+
public final class AttributeErrorBuiltins extends PythonBuiltins {
92+
93+
public static final TpSlots SLOTS = AttributeErrorBuiltinsSlotsGen.SLOTS;
94+
95+
@Override
96+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
97+
return AttributeErrorBuiltinsFactory.getFactories();
98+
}
99+
100+
private static final int IDX_NAME = 0;
101+
private static final int IDX_OBJ = 1;
102+
private static final int NUM_ATTRS = IDX_OBJ + 1;
103+
104+
private static final TruffleString T_NAME = tsLiteral("name");
105+
private static final TruffleString T_OBJ = tsLiteral("obj");
106+
107+
private static final BaseExceptionAttrNode.StorageFactory ATTR_FACTORY = (args) -> new Object[NUM_ATTRS];
108+
109+
public static Object[] dataForObjKey(Object obj, Object key) {
110+
return new Object[]{key, obj};
111+
}
112+
113+
@Slot(value = SlotKind.tp_init, isComplex = true)
114+
@SlotSignature(minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true)
115+
@GenerateNodeFactory
116+
abstract static class InitNode extends PythonVarargsBuiltinNode {
117+
118+
@Specialization
119+
static Object init(PBaseException self, Object[] args, PKeyword[] kwargs,
120+
@Bind Node inliningTarget,
121+
@Cached BaseExceptionBuiltins.BaseExceptionInitNode baseExceptionInitNode,
122+
@Cached TruffleString.EqualNode equalNameNode,
123+
@Cached TruffleString.EqualNode equalObjNode,
124+
@Cached InlinedLoopConditionProfile loopProfile,
125+
@Cached PRaiseNode raiseNode) {
126+
baseExceptionInitNode.execute(self, args);
127+
Object[] attrs = new Object[NUM_ATTRS];
128+
loopProfile.profileCounted(inliningTarget, kwargs.length);
129+
for (int i = 0; loopProfile.inject(inliningTarget, i < kwargs.length); i++) {
130+
PKeyword kw = kwargs[i];
131+
TruffleString kwName = kw.getName();
132+
if (equalNameNode.execute(kwName, T_NAME, TS_ENCODING)) {
133+
attrs[IDX_NAME] = kw.getValue();
134+
} else if (equalObjNode.execute(kwName, T_OBJ, TS_ENCODING)) {
135+
attrs[IDX_OBJ] = kw.getValue();
136+
} else {
137+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.S_IS_AN_INVALID_ARG_FOR_S, kw.getName(), "AttributeError");
138+
}
139+
}
140+
self.setExceptionAttributes(attrs);
141+
return PNone.NONE;
142+
}
143+
}
144+
145+
@Builtin(name = "name", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true, doc = "attribute name")
146+
@GenerateNodeFactory
147+
public abstract static class NameNode extends PythonBinaryBuiltinNode {
148+
@Specialization
149+
static Object generic(PBaseException self, Object value,
150+
@Cached BaseExceptionAttrNode attrNode) {
151+
return attrNode.execute(self, value, IDX_NAME, ATTR_FACTORY);
152+
}
153+
}
154+
155+
@Builtin(name = "obj", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true, doc = "object")
156+
@GenerateNodeFactory
157+
public abstract static class ObjNode extends PythonBinaryBuiltinNode {
158+
@Specialization
159+
static Object generic(PBaseException self, Object value,
160+
@Cached BaseExceptionAttrNode attrNode) {
161+
return attrNode.execute(self, value, IDX_OBJ, ATTR_FACTORY);
162+
}
163+
}
164+
165+
@Builtin(name = J___GETSTATE__, minNumOfPositionalArgs = 1)
166+
@GenerateNodeFactory
167+
public abstract static class GetStateNode extends PythonUnaryBuiltinNode {
168+
169+
@Specialization
170+
static Object get(PBaseException self,
171+
@Bind("this") Node inliningTarget,
172+
@Cached BaseExceptionAttrNode attrNode,
173+
@Cached GetDictIfExistsNode getDictIfExistsNode,
174+
@Cached HashingStorageNodes.HashingStorageSetItem setHashingStorageItem,
175+
@Cached HashingStorageNodes.HashingStorageCopy copyStorageNode,
176+
@Bind PythonLanguage language) {
177+
PDict dict = getDictIfExistsNode.execute(self);
178+
/*
179+
* Note from CPython: We specifically are not pickling the obj attribute since there are
180+
* many cases where it is unlikely to be picklable.
181+
*/
182+
Object name = attrNode.get(self, IDX_NAME, ATTR_FACTORY);
183+
if (name != null) {
184+
HashingStorage storage = (dict != null) ? copyStorageNode.execute(inliningTarget, dict.getDictStorage()) : EmptyStorage.INSTANCE;
185+
storage = setHashingStorageItem.execute(inliningTarget, storage, T_NAME, name);
186+
return PFactory.createDict(language, storage);
187+
} else if (dict != null) {
188+
return dict;
189+
} else {
190+
return PNone.NONE;
191+
}
192+
}
193+
}
194+
195+
@Builtin(name = J___REDUCE__, minNumOfPositionalArgs = 1)
196+
@GenerateNodeFactory
197+
public abstract static class ReduceNode extends PythonUnaryBuiltinNode {
198+
199+
@Specialization
200+
static Object reduce(VirtualFrame frame, PBaseException self,
201+
@Bind("this") Node inliningTarget,
202+
@Cached GetClassNode getClassNode,
203+
@Cached ExceptionNodes.GetArgsNode getArgsNode,
204+
@Cached GetStateNode getStateNode,
205+
@Bind PythonLanguage language) {
206+
Object clazz = getClassNode.execute(inliningTarget, self);
207+
Object args = getArgsNode.execute(inliningTarget, self);
208+
Object state = getStateNode.execute(frame, self);
209+
return PFactory.createTuple(language, new Object[]{clazz, args, state});
210+
}
211+
}
212+
213+
@GenerateInline
214+
@GenerateCached(false)
215+
@GenerateUncached
216+
public abstract static class SetAttributeErrorContext extends Node {
217+
public abstract PException execute(Node inliningTarget, PException e, Object obj, Object name);
218+
219+
@Specialization
220+
static PException set(Node inliningTarget, PException e, Object obj, Object name,
221+
@Cached BuiltinClassProfiles.IsBuiltinObjectProfile errorProfile,
222+
@Cached BaseExceptionAttrNode attrNode,
223+
@Cached InlinedConditionProfile writeAttrsProfile) {
224+
e.expectAttributeError(inliningTarget, errorProfile);
225+
if (writeAttrsProfile.profile(inliningTarget, e.getUnreifiedException() instanceof PBaseException exception &&
226+
exception.getExceptionAttributes() != null &&
227+
exception.getExceptionAttributes()[IDX_NAME] == null && exception.getExceptionAttributes()[IDX_OBJ] == null)) {
228+
PBaseException exception = (PBaseException) e.getUnreifiedException();
229+
attrNode.set(exception, name, IDX_NAME, ATTR_FACTORY);
230+
attrNode.set(exception, obj, IDX_OBJ, ATTR_FACTORY);
231+
}
232+
throw e;
233+
}
234+
}
235+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/ModuleBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ private static PDict createDict(Node inliningTarget, PythonModule self, SetDictN
251251

252252
@Slot(value = SlotKind.tp_getattro, isComplex = true)
253253
@GenerateNodeFactory
254-
public abstract static class ModuleGetattritbuteNode extends GetAttrBuiltinNode {
254+
public abstract static class ModuleGetattributeNode extends GetAttrBuiltinNode {
255255
@Specialization
256256
static Object getattributeStr(VirtualFrame frame, PythonModule self, TruffleString key,
257257
@Shared @Cached ObjectBuiltins.GetAttributeNode objectGetattrNode,

0 commit comments

Comments
 (0)