Skip to content

Commit d591da4

Browse files
committed
[GR-42711] Make foreign numbers inherit from ForeignNumberType, foreign strings from str and fix TCK
PullRequest: graalpython/3513
2 parents 371a922 + 245aab9 commit d591da4

File tree

30 files changed

+1319
-1123
lines changed

30 files changed

+1319
-1123
lines changed

CHANGELOG.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ language runtime. The main focus is on user-observable behavior of the engine.
99
* When calling a method on a foreign object in Python code, Python methods are now prioritized over foreign members.
1010
* Added `polyglot.register_interop_type` and `@polyglot.interop_type` to define custom Python methods for a given foreign class/type. See [the documentation](https://github.com/oracle/graalpython/blob/master/docs/user/Interoperability.md#the-interoperability-extension-api) for more information.
1111
* Foreign objects are now given a Python class corresponding to their interop traits.
12-
** Foreign lists now inherit from Python `list`, foreign dictionaries from `dict`, foreign iterators from `iterator`, foreign exceptions from `BaseException` and foreign none/null from `NoneType`.
13-
** This means all Python methods of these types are available on the corresponding foreign objects, which behave as close as possible as if they were Python objects.
14-
** See [the documentation](https://github.com/oracle/graalpython/blob/master/docs/user/Interoperability.md#interacting-with-foreign-objects-from-python-scripts) for more information.
12+
* Foreign lists now inherit from Python `list`, foreign dictionaries from `dict`, foreign strings from `str`, foreign iterators from `iterator`, foreign exceptions from `BaseException`, foreign numbers from `ForeignNumberType` and foreign none/null from `NoneType`.
13+
* This means all Python methods of these types are available on the corresponding foreign objects, which behave as close as possible as if they were Python objects.
14+
* See [the documentation](https://github.com/oracle/graalpython/blob/master/docs/user/Interoperability.md#interacting-with-foreign-objects-from-python-scripts) for more information.
1515
* Remove support for running with Sulong managed both in embeddings as well as through the `graalpy-managed` launcher.
1616

1717
## Version 24.1.0
@@ -40,9 +40,9 @@ language runtime. The main focus is on user-observable behavior of the engine.
4040
* `multiprocessing` module now uses the `spawn` method (creates new processes) by default. The formerly default method that uses threads and multiple Truffle contexts can be selected using `multiprocessing.set_start_method('graalpy')`.
4141
* `polyglot` module: add API to redefine Truffle interop messages for external / user defined types. For more details see [The Truffle Interoperability Extension API](docs/user/Interoperability.md).
4242
* Adding integration with jBang (https://www.jbang.dev/)
43-
** running example via `jbang hello@oracle/graalpython` or `jbang hello@oracle/graalpython "print(1*4)"`
44-
** creating new script via: `jbang init --template=graalpy@oracle/graalpython myscript.java`
45-
** creating new script with local maven repo for testing: `jbang init --template=graalpy_local_repo@oracle/graalpython -Dpath_to_local_repo=/absolute/path/to/local/maven/repository myscript.java'
43+
* running example via `jbang hello@oracle/graalpython` or `jbang hello@oracle/graalpython "print(1*4)"`
44+
* creating new script via: `jbang init --template=graalpy@oracle/graalpython myscript.java`
45+
* creating new script with local maven repo for testing: `jbang init --template=graalpy_local_repo@oracle/graalpython -Dpath_to_local_repo=/absolute/path/to/local/maven/repository myscript.java'
4646

4747
## Version 23.1.0
4848
* GraalPy distributions (previously known as GraalPy Enterprise) are now available under the [GFTC license](https://www.oracle.com/downloads/licenses/graal-free-license.html). The community builds published on Github have been renamed to `graalpy-community-<version>-<os>-<arch>.tar.gz`.

docs/user/Interoperability.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,15 @@ h == {1: 2, 3: 6} # True
9292
Specifically:
9393
* Foreign lists inherit from Python `list`
9494
* Foreign dictionaries inherit from `dict`
95+
* Foreign strings inherit from `str`
9596
* Foreign iterators inherit from `iterator`
9697
* Foreign exceptions inherit from `BaseException`
98+
* Foreign numbers inherit from `ForeignNumberType` (since `InteropLibrary` has no way to differentiate integers and floats, but see below)
9799
* Foreign none/null inherit from `NoneType`
100+
* Other foreign objects inherit from `foreign`
101+
102+
Note that Java primitives `byte`, `short`, `int`, `long` and `BigInteger` values are considered Python `int` objects,
103+
and Java primitives `float`, `double` values are considered Python `float` objects.
98104

99105
## Interacting with other dynamic languages from Python scripts
100106

graalpython/com.oracle.graal.python.tck/src/com/oracle/graal/python/tck/PythonProvider.java

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -78,6 +78,7 @@
7878
import org.graalvm.polyglot.tck.ResultVerifier;
7979
import org.graalvm.polyglot.tck.Snippet;
8080
import org.graalvm.polyglot.tck.TypeDescriptor;
81+
import org.junit.Assert;
8182

8283
public class PythonProvider implements LanguageProvider {
8384

@@ -226,28 +227,37 @@ public Collection<? extends Snippet> createValueConstructors(Context context) {
226227
public Collection<? extends Snippet> createExpressions(Context context) {
227228
List<Snippet> snippets = new ArrayList<>();
228229

230+
TypeDescriptor numeric = union(BOOLEAN, NUMBER);
231+
TypeDescriptor numericArray = array(numeric);
232+
229233
// @formatter:off
230-
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", NUMBER, new NonPrimitiveNumberParameterThrows(AddVerifier.INSTANCE), union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
231-
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", union(STRING, array(ANY)), AddVerifier.INSTANCE, union(BOOLEAN, NUMBER), union(STRING, array(ANY)));
232-
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", union(STRING, array(ANY)), AddVerifier.INSTANCE, union(STRING, array(ANY)), union(BOOLEAN, NUMBER));
234+
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", NUMBER, new NonPrimitiveNumberParameterThrows(AddVerifier.INSTANCE), numeric, numeric);
235+
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", union(STRING, array(ANY)), AddVerifier.INSTANCE, numeric, union(STRING, array(ANY)));
236+
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", union(STRING, array(ANY)), AddVerifier.INSTANCE, union(STRING, array(ANY)), numeric);
233237
addExpressionSnippet(context, snippets, "+", "lambda x, y: x + y", union(STRING, array(ANY)), AddVerifier.INSTANCE, union(STRING, array(ANY)), union(STRING, array(ANY)));
234-
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", NUMBER, new NonPrimitiveNumberParameterThrows(MulVerifier.INSTANCE), union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
235-
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", union(STRING, array(ANY)), MulVerifier.INSTANCE, union(BOOLEAN, NUMBER), union(STRING, array(ANY)));
236-
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", union(STRING, array(ANY)), MulVerifier.INSTANCE, union(STRING, array(ANY)), union(BOOLEAN, NUMBER));
238+
239+
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", NUMBER, new NonPrimitiveNumberParameterThrows(MulVerifier.INSTANCE), numeric, numeric);
240+
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", union(STRING, array(ANY)), MulVerifier.INSTANCE, numeric, union(STRING, array(ANY)));
241+
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", union(STRING, array(ANY)), MulVerifier.INSTANCE, union(STRING, array(ANY)), numeric);
237242
addExpressionSnippet(context, snippets, "*", "lambda x, y: x * y", union(STRING, array(ANY)), MulVerifier.INSTANCE, union(STRING, array(ANY)), union(STRING, array(ANY)));
238243

239-
addExpressionSnippet(context, snippets, "-", "lambda x, y: x - y", NUMBER, NonPrimitiveNumberParameterThrows.INSTANCE, union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
244+
addExpressionSnippet(context, snippets, "-", "lambda x, y: x - y", NUMBER, NonPrimitiveNumberParameterThrows.INSTANCE, numeric, numeric);
240245

241-
addExpressionSnippet(context, snippets, "/", "lambda x, y: x / y", NUMBER, new NonPrimitiveNumberParameterThrows(PDivByZeroVerifier.INSTANCE), union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
246+
addExpressionSnippet(context, snippets, "/", "lambda x, y: x / y", NUMBER, new NonPrimitiveNumberParameterThrows(PDivByZeroVerifier.INSTANCE), numeric, numeric);
242247

243248
addExpressionSnippet(context, snippets, "list-from-foreign", "lambda x: list(x)", array(ANY), union(STRING, iterable(ANY), iterator(ANY), array(ANY), hash(ANY, ANY)));
244249

245250
addExpressionSnippet(context, snippets, "==", "lambda x, y: x == y", BOOLEAN, ANY, ANY);
246251
addExpressionSnippet(context, snippets, "!=", "lambda x, y: x != y", BOOLEAN, ANY, ANY);
247-
addExpressionSnippet(context, snippets, ">", "lambda x, y: x > y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
248-
addExpressionSnippet(context, snippets, ">=", "lambda x, y: x >= y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
249-
addExpressionSnippet(context, snippets, "<", "lambda x, y: x < y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
250-
addExpressionSnippet(context, snippets, "<=", "lambda x, y: x <= y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, union(BOOLEAN, NUMBER), union(BOOLEAN, NUMBER));
252+
253+
addExpressionSnippet(context, snippets, ">", "lambda x, y: x > y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, numeric, numeric);
254+
addExpressionSnippet(context, snippets, ">", "lambda x, y: x > y", BOOLEAN, ExpectTypeErrorForDifferentPythonSequenceTypes.INSTANCE, numericArray, numericArray);
255+
addExpressionSnippet(context, snippets, ">=", "lambda x, y: x >= y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, numeric, numeric);
256+
addExpressionSnippet(context, snippets, ">=", "lambda x, y: x >= y", BOOLEAN, ExpectTypeErrorForDifferentPythonSequenceTypes.INSTANCE, numericArray, numericArray);
257+
addExpressionSnippet(context, snippets, "<", "lambda x, y: x < y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, numeric, numeric);
258+
addExpressionSnippet(context, snippets, "<", "lambda x, y: x < y", BOOLEAN, ExpectTypeErrorForDifferentPythonSequenceTypes.INSTANCE, numericArray, numericArray);
259+
addExpressionSnippet(context, snippets, "<=", "lambda x, y: x <= y", BOOLEAN, NonPrimitiveNumberParameterThrows.INSTANCE, numeric, numeric);
260+
addExpressionSnippet(context, snippets, "<=", "lambda x, y: x <= y", BOOLEAN, ExpectTypeErrorForDifferentPythonSequenceTypes.INSTANCE, numericArray, numericArray);
251261

252262
addExpressionSnippet(context, snippets, "isinstance", "lambda x, y: isinstance(x, y)", BOOLEAN, ANY, META_OBJECT);
253263
addExpressionSnippet(context, snippets, "issubclass", "lambda x, y: issubclass(x, y)", BOOLEAN, META_OBJECT, META_OBJECT);
@@ -360,6 +370,35 @@ private static Source createSource(String resourceName) throws IOException {
360370
private abstract static class PResultVerifier implements ResultVerifier {
361371
}
362372

373+
private static class ExpectTypeErrorForDifferentPythonSequenceTypes extends PResultVerifier {
374+
375+
public void accept(SnippetRun snippetRun) throws PolyglotException {
376+
List<? extends Value> parameters = snippetRun.getParameters();
377+
assert parameters.size() == 2;
378+
379+
Value a = parameters.get(0);
380+
Value b = parameters.get(1);
381+
382+
// If both parameter values are Python sequences, then ignore if they have different
383+
// types. E.g. ignore '(1,2) < [3,4]'.
384+
if (a.hasArrayElements() && b.hasArrayElements() && isPythonObject(a) && isPythonObject(b) && !a.getMetaObject().equals(b.getMetaObject())) {
385+
// Expect TypeError
386+
var exception = snippetRun.getException();
387+
assert exception != null;
388+
Assert.assertEquals("TypeError", exception.getGuestObject().getMetaObject().getMetaQualifiedName());
389+
} else {
390+
ResultVerifier.getDefaultResultVerifier().accept(snippetRun);
391+
}
392+
}
393+
394+
private static boolean isPythonObject(Value value) {
395+
var metaObject = value.getMetaObject();
396+
return metaObject.hasMember("__mro__");
397+
}
398+
399+
private static final ExpectTypeErrorForDifferentPythonSequenceTypes INSTANCE = new ExpectTypeErrorForDifferentPythonSequenceTypes();
400+
}
401+
363402
/**
364403
* Only accepts exact matches of types.
365404
*/
@@ -378,16 +417,16 @@ public void accept(SnippetRun snippetRun) throws PolyglotException {
378417
if (par0.getMetaObject() == par1.getMetaObject()) {
379418
assert snippetRun.getException() == null;
380419
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
381-
assert array(ANY).isAssignable(resultType);
420+
assert array(ANY).isAssignable(resultType) : resultType;
382421
}
383422
} else if (par0.isString() && par1.isString()) {
384423
assert snippetRun.getException() == null;
385424
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
386-
assert STRING.isAssignable(resultType);
425+
assert STRING.isAssignable(resultType) : resultType;
387426
} else if ((par0.isNumber() || par0.isBoolean()) && (par1.isNumber() || par1.isBoolean())) {
388427
assert snippetRun.getException() == null;
389428
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
390-
assert NUMBER.isAssignable(resultType);
429+
assert NUMBER.isAssignable(resultType) : resultType;
391430
} else {
392431
assert snippetRun.getException() != null;
393432
TypeDescriptor argType = union(STRING, BOOLEAN, NUMBER, array(ANY));
@@ -426,17 +465,17 @@ public void accept(SnippetRun snippetRun) throws PolyglotException {
426465
// string * number => string
427466
assert snippetRun.getException() == null;
428467
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
429-
assert STRING.isAssignable(resultType);
468+
assert STRING.isAssignable(resultType) : resultType;
430469
} else if ((par0.isNumber() || par0.isBoolean()) && (par1.isNumber() || par1.isBoolean())) {
431-
// number * number has greater precendence than array * number
470+
// number * number has greater precedence than array * number
432471
assert snippetRun.getException() == null;
433472
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
434-
assert NUMBER.isAssignable(resultType) : resultType.toString();
473+
assert NUMBER.isAssignable(resultType) : resultType;
435474
} else if (isArrayMul(par0, par1) || isArrayMul(par1, par0)) {
436475
// array * number => array
437476
assert snippetRun.getException() == null;
438477
TypeDescriptor resultType = TypeDescriptor.forValue(snippetRun.getResult());
439-
assert array(ANY).isAssignable(resultType);
478+
assert array(ANY).isAssignable(resultType) : resultType;
440479
} else {
441480
assert snippetRun.getException() != null;
442481
TypeDescriptor argType = union(STRING, BOOLEAN, NUMBER, array(ANY));
@@ -461,34 +500,34 @@ public void accept(SnippetRun snippetRun) throws PolyglotException {
461500
List<? extends Value> parameters = snippetRun.getParameters();
462501
assert parameters.size() == 2;
463502

464-
Value par0 = parameters.get(0);
465-
Value par1 = parameters.get(1);
503+
Value self = parameters.get(0);
504+
Value index = parameters.get(1);
466505

467506
long len = -1;
468507

469-
if (par0.hasArrayElements()) {
470-
len = par0.getArraySize();
471-
} else if (par0.isString()) {
472-
len = par0.asString().length();
508+
if (self.isString()) {
509+
len = self.asString().length();
510+
} else if (self.hasArrayElements()) {
511+
len = self.getArraySize();
473512
}
474513
if (len >= 0) {
475514
int idx;
476-
if (par1.isBoolean()) {
477-
idx = par1.asBoolean() ? 1 : 0;
478-
} else if (par1.isNumber() && par1.fitsInInt()) {
479-
idx = par1.asInt();
515+
if (index.isBoolean()) {
516+
idx = index.asBoolean() ? 1 : 0;
517+
} else if (index.isNumber() && index.fitsInInt()) {
518+
idx = index.asInt();
480519
} else {
481-
assert snippetRun.getException() != null;
520+
assert snippetRun.getException() != null : snippetRun.getResult();
482521
return;
483522
}
484-
if ((idx >= 0 && len > idx) || (idx < 0 && idx + len >= 0 && len > idx + len)) {
485-
assert snippetRun.getException() == null : snippetRun.getException().toString();
523+
if ((idx >= 0 && idx < len) || (idx < 0 && idx + len >= 0 && idx + len < len)) {
524+
assert snippetRun.getException() == null : snippetRun.getException();
486525
} else {
487-
assert snippetRun.getException() != null;
526+
assert snippetRun.getException() != null : snippetRun.getResult();
488527
}
489-
} else if (par0.hasHashEntries()) {
490-
if (par1.getMetaObject() != null) {
491-
String metaName = par1.getMetaObject().getMetaQualifiedName();
528+
} else if (self.hasHashEntries()) {
529+
if (index.getMetaObject() != null) {
530+
String metaName = index.getMetaObject().getMetaQualifiedName();
492531
for (String s : UNHASHABLE_TYPES) {
493532
if (metaName.equals(s)) {
494533
// those don't work, but that's expected
@@ -497,11 +536,11 @@ public void accept(SnippetRun snippetRun) throws PolyglotException {
497536
}
498537
}
499538
}
500-
Value v = par0.getHashValueOrDefault(par1, PythonProvider.class.getName());
539+
Value v = self.getHashValueOrDefault(index, PythonProvider.class.getName());
501540
if (v.isString() && v.asString().equals(PythonProvider.class.getName())) {
502-
assert snippetRun.getException() != null;
541+
assert snippetRun.getException() != null : snippetRun.getResult();
503542
} else {
504-
assert snippetRun.getException() == null : snippetRun.getException().toString();
543+
assert snippetRun.getException() == null : snippetRun.getException();
505544
}
506545
} else {
507546
// argument type error, rethrow

graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/interop/JavaInteropTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def foo(obj):
496496
foo.execute(ProxyObject.fromMap(m));
497497
}
498498

499-
public class JavaObject {
499+
public static class JavaObject {
500500
public byte byteValue = 1;
501501
public short shortValue = 2;
502502
public int intValue = 3;
@@ -563,7 +563,7 @@ public void accessJavaObjectFields() throws IOException {
563563
"5.0 <class 'float'>\n" +
564564
"6.0 <class 'float'>\n" +
565565
"True <class 'bool'>\n" +
566-
"c <class 'str'>\n", out.toString("UTF-8"));
566+
"c <class 'polyglot.ForeignString'>\n", out.toString("UTF-8"));
567567
}
568568

569569
@Test
@@ -590,7 +590,7 @@ public void accessJavaObjectGetters() throws IOException {
590590
"5.0 <class 'float'>\n" +
591591
"6.0 <class 'float'>\n" +
592592
"True <class 'bool'>\n" +
593-
"c <class 'str'>\n", out.toString("UTF-8"));
593+
"c <class 'polyglot.ForeignString'>\n", out.toString("UTF-8"));
594594
}
595595

596596
@Test

0 commit comments

Comments
 (0)