From jython-checkins at python.org Thu Apr 4 04:12:25 2019 From: jython-checkins at python.org (jeff.allen) Date: Thu, 04 Apr 2019 08:12:25 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Respect_AST=2Elineno_in_co?= =?utf-8?q?mpiled_code_=28fixes_=232635=29=2E?= Message-ID: <20190404081225.1.B2A12E69B23752FA@mg.python.org> https://hg.python.org/jython/rev/23ded47db8ca changeset: 8234:23ded47db8ca user: Jim Peterson date: Thu Apr 04 08:06:04 2019 +0100 summary: Respect AST.lineno in compiled code (fixes #2635). This change makes getLine() and getCharPositionInLine() protected, adding getLine() and getCol_offset() to PythonTree, which calls the protected methods. All references outside the PythonTree descendents call getLineno() and getCol_offset(), instead. files: ACKNOWLEDGMENTS | 1 + Lib/test/test_ast.py | 15 ++++++++- NEWS | 1 + src/org/python/antlr/ParseException.java | 2 +- src/org/python/antlr/PythonTree.java | 12 ++++++- src/org/python/compiler/CodeCompiler.java | 16 +++++----- src/org/python/compiler/Module.java | 2 +- src/org/python/compiler/ScopesCompiler.java | 4 +- src/org/python/core/ParserFacade.java | 4 +- 9 files changed, 40 insertions(+), 17 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -184,6 +184,7 @@ Raymond Ferguson yishenggudou (??) Andrew Kuchling + Jim Peterson Local Variables: mode: indented-text diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1,4 +1,4 @@ -import sys, itertools, unittest +import sys, itertools, unittest, traceback from test import test_support import ast @@ -290,6 +290,19 @@ self.assertEqual(ast.literal_eval('(True, False, None)'), (True, False, None)) self.assertRaises(ValueError, ast.literal_eval, 'foo()') + def test_uses_lineno(self): + node = ast.Module(body=[ast.Expr(value=ast.Name(id='x', ctx=ast.Load()), lineno=42)]) + node = ast.fix_missing_locations(node) + exc = None + try: + exec compile(node,'','exec') + except: + exc = traceback.format_tb(sys.exc_info()[2]) + assert exc is not None + + # can only check the last line of the stack trace: + self.assertEqual(exc[-1],' File "", line 42, in \n') + def test_main(): #XXX: AST pickling is left as a TODO for now. diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Development tip Bugs fixed + - [ 2635 ] AST.lineno ignored by compile - [ 2744 ] Support buffer type in marshal.dump(s) - [ 2077 ] marshal doesn't raise error when fed unmarshalable object - [ 2445 ] Eclipse's DelegatingFeatureMap has MRO conflict diff --git a/src/org/python/antlr/ParseException.java b/src/org/python/antlr/ParseException.java --- a/src/org/python/antlr/ParseException.java +++ b/src/org/python/antlr/ParseException.java @@ -35,7 +35,7 @@ * n must not be null to use this constructor */ public ParseException(String message, PythonTree n) { - this(message, n.getLine(), n.getCharPositionInLine()); + this(message, n.getLineno(), n.getCol_offset()); this.node = n; this.token = n.getToken(); } diff --git a/src/org/python/antlr/PythonTree.java b/src/org/python/antlr/PythonTree.java --- a/src/org/python/antlr/PythonTree.java +++ b/src/org/python/antlr/PythonTree.java @@ -78,7 +78,7 @@ return node.getText(); } - public int getLine() { + protected int getLine() { if (node.getToken()==null || node.getToken().getLine()==0) { if ( getChildCount()>0 ) { return getChild(0).getLine(); @@ -88,7 +88,7 @@ return node.getToken().getLine(); } - public int getCharPositionInLine() { + protected int getCharPositionInLine() { Token token = node.getToken(); if (token==null || token.getCharPositionInLine()==-1) { if (getChildCount()>0) { @@ -105,6 +105,14 @@ return token.getCharPositionInLine(); } + public int getLineno() { + return getLine(); + } + + public int getCol_offset() { + return getCharPositionInLine(); + } + public int getTokenStartIndex() { return node.getTokenStartIndex(); } diff --git a/src/org/python/compiler/CodeCompiler.java b/src/org/python/compiler/CodeCompiler.java --- a/src/org/python/compiler/CodeCompiler.java +++ b/src/org/python/compiler/CodeCompiler.java @@ -183,7 +183,7 @@ } public void setline(PythonTree node) throws Exception { - setline(node.getLine()); + setline(node.getLineno()); } public void set(PythonTree node) throws Exception { @@ -509,7 +509,7 @@ scope.setup_closure(); scope.dump(); module.codeConstant(new Suite(node, node.getInternalBody()), name, true, className, false, - false, node.getLine(), scope, cflags).get(code); + false, node.getLineno(), scope, cflags).get(code); Str docStr = getDocStr(node.getInternalBody()); if (docStr != null) { @@ -2168,7 +2168,7 @@ code.ldc("append"); code.invokevirtual(p(PyObject.class), "__getattr__", sig(PyObject.class, String.class)); - String tmp_append = "_[" + node.getLine() + "_" + node.getCharPositionInLine() + "]"; + String tmp_append = "_[" + node.getLineno() + "_" + node.getCol_offset() + "]"; java.util.List args = new ArrayList(); args.add(node.getInternalElt()); @@ -2312,7 +2312,7 @@ scope.setup_closure(); scope.dump(); - module.codeConstant(retSuite, name, true, className, false, false, node.getLine(), scope, + module.codeConstant(retSuite, name, true, className, false, false, node.getLineno(), scope, cflags).get(code); if (!makeClosure(scope)) { @@ -2387,8 +2387,8 @@ // Make code object out of suite module.codeConstant(new Suite(node, node.getInternalBody()), name, false, name, - getDocStr(node.getInternalBody()), true, false, node.getLine(), scope, cflags).get( - code); + getDocStr(node.getInternalBody()), true, false, node.getLineno(), scope, cflags) + .get(code); // Make class out of name, bases, and code if (!makeClosure(scope)) { @@ -2608,7 +2608,7 @@ java.util.List bod = new ArrayList(); bod.add(n); module.codeConstant(new Suite(node, bod), "", true, className, false, false, - node.getLine(), scope, cflags).get(code); + node.getLineno(), scope, cflags).get(code); code.aconst_null(); if (!makeClosure(scope)) { @@ -2681,7 +2681,7 @@ java.util.List bod = new ArrayList(); bod.add(n); module.codeConstant(new Suite(node, bod), "", true, className, false, false, - node.getLine(), scope, cflags).get(code); + node.getLineno(), scope, cflags).get(code); code.aconst_null(); if (!makeClosure(scope)) { diff --git a/src/org/python/compiler/Module.java b/src/org/python/compiler/Module.java --- a/src/org/python/compiler/Module.java +++ b/src/org/python/compiler/Module.java @@ -685,7 +685,7 @@ if (!err) { try { Py.warning(Py.SyntaxWarning, msg, (sfilename != null) ? sfilename : "?", - node.getLine(), null, Py.None); + node.getLineno(), null, Py.None); return; } catch (PyException e) { if (!e.match(Py.SyntaxWarning)) { diff --git a/src/org/python/compiler/ScopesCompiler.java b/src/org/python/compiler/ScopesCompiler.java --- a/src/org/python/compiler/ScopesCompiler.java +++ b/src/org/python/compiler/ScopesCompiler.java @@ -285,7 +285,7 @@ @Override public Object visitListComp(ListComp node) throws Exception { - String tmp = "_[" + node.getLine() + "_" + node.getCharPositionInLine() + String tmp = "_[" + node.getLineno() + "_" + node.getCol_offset() + "]"; cur.addBound(tmp); traverse(node); @@ -328,7 +328,7 @@ visit(generators.get(0).getInternalIter()); } String bound_exp = "_(x)"; - String tmp = "_(" + node.getLine() + "_" + node.getCharPositionInLine() + String tmp = "_(" + node.getLineno() + "_" + node.getCol_offset() + ")"; def(tmp); ArgListCompiler ac = new ArgListCompiler(); diff --git a/src/org/python/core/ParserFacade.java b/src/org/python/core/ParserFacade.java --- a/src/org/python/core/ParserFacade.java +++ b/src/org/python/core/ParserFacade.java @@ -84,8 +84,8 @@ int line=e.line; int col=e.charPositionInLine; if (node != null) { - line = node.getLine(); - col = node.getCharPositionInLine(); + line = node.getLineno(); + col = node.getCol_offset(); } String text= getLine(reader, line); String msg = e.getMessage(); -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Thu Apr 4 04:12:25 2019 From: jython-checkins at python.org (jeff.allen) Date: Thu, 04 Apr 2019 08:12:25 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Improve_test_coverage_in_t?= =?utf-8?q?est=5Fdict=5Fjy_and_conformance_to_dict_of_Map_proxies=2E?= Message-ID: <20190404081225.1.E1595FDC0ABC1EFE@mg.python.org> https://hg.python.org/jython/rev/65c4b774a715 changeset: 8233:65c4b774a715 user: Jeff Allen date: Thu Apr 04 06:24:01 2019 +0100 summary: Improve test coverage in test_dict_jy and conformance to dict of Map proxies. This change set re-works JavaProxyMap in several respects. popitem() returns a tuple and avoids getting the whole key set as an array. update is restructured for clarity and to avoid an instance field. We pay particular attention to the consistent handling of None as null internally, and test that Java containers that reject null raise ValueError on assignment of None. files: Lib/test/test_dict.py | 70 +- Lib/test/test_dict_jy.py | 241 +++++++++-- src/org/python/core/JavaProxyMap.java | 282 ++++++++----- 3 files changed, 400 insertions(+), 193 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1,7 +1,5 @@ import unittest from test import test_support -from java.util import Map -from java.util.concurrent import ConcurrentMap import UserDict, random, string import gc, weakref @@ -9,10 +7,14 @@ class DictTest(unittest.TestCase): - _class = None + type2test = dict def _make_dict(self, pydict): - return pydict if not self._class else self._class(pydict) + 'Create a self.type2test from pydict, or just return it if we are testing dict' + if self.type2test is dict: + return pydict + else: + return self.type2test(pydict) def test_constructor(self): # calling built-in types without argument must return empty @@ -217,7 +219,10 @@ self.assertRaises(ValueError, d.update, [(1, 2, 3)]) def test_fromkeys(self): - self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + Dict = self.type2test + + self.assertEqual(Dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + d = self._make_dict({}) self.assertIsNot(d.fromkeys('abc'), d) self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) @@ -226,23 +231,25 @@ def g(): yield 1 self.assertEqual(d.fromkeys(g()), {1:None}) - self.assertRaises(TypeError, {}.fromkeys, 3) - class dictlike(dict): pass + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3) + + class dictlike(Dict): pass self.assertEqual(dictlike.fromkeys('a'), {'a':None}) self.assertEqual(dictlike().fromkeys('a'), {'a':None}) self.assertIsInstance(dictlike.fromkeys('a'), dictlike) self.assertIsInstance(dictlike().fromkeys('a'), dictlike) - class mydict(dict): + + class mydict(Dict): def __new__(cls): return UserDict.UserDict() ud = mydict.fromkeys('ab') self.assertEqual(ud, {'a':None, 'b':None}) self.assertIsInstance(ud, UserDict.UserDict) - self.assertRaises(TypeError, dict.fromkeys) + self.assertRaises(TypeError, Dict.fromkeys) class Exc(Exception): pass - class baddict1(dict): + class baddict1(Dict): def __init__(self): raise Exc() @@ -254,17 +261,25 @@ def next(self): raise Exc() - self.assertRaises(Exc, dict.fromkeys, BadSeq()) + self.assertRaises(Exc, Dict.fromkeys, BadSeq()) - class baddict2(dict): + class baddict2(Dict): def __setitem__(self, key, value): raise Exc() self.assertRaises(Exc, baddict2.fromkeys, [1]) # test fast path for dictionary inputs - d = dict(zip(range(6), range(6))) - self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6))) + d = Dict(zip(range(6), range(6))) + self.assertEqual(Dict.fromkeys(d, 0), Dict(zip(range(6), [0]*6))) + + class baddict3(Dict): + def __new__(cls): + return d + d = self._make_dict({i : i for i in range(10)}) + res = d.copy() + res.update(a=None, b=None, c=None) + self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res) def test_copy(self): d = self._make_dict({1:1, 2:2, 3:3}) @@ -312,8 +327,7 @@ x.fail = True self.assertRaises(Exc, d.setdefault, x, []) - @unittest.skipIf(test_support.is_jython and test_support.get_java_version() >= (9,), - "Fails on Java 9+. See b.j.o. issue 2711.") + @unittest.skipIf(test_support.is_jython, "CPython specific. See bjo issue 2711.") def test_setdefault_atomic(self): # Issue #13521: setdefault() calls __hash__ and __eq__ only once. class Hashed(object): @@ -328,8 +342,6 @@ return id(self) == id(other) hashed1 = Hashed() y = self._make_dict({hashed1: 5}) - if isinstance(y, Map) and not isinstance(y, ConcurrentMap): - raise unittest.SkipTest("java.util.Map objects that do not implement ConcurrentMap have no concurrency guarantees") # given that there are potentially multiple copies of the # above dict in self._make_dict, record the hash_count so it # can be subtracted out @@ -454,15 +466,16 @@ d1 < d2 def test_missing(self): + Dict = self.type2test # Make sure dict doesn't have a __missing__ method - self.assertFalse(hasattr(dict, "__missing__")) + self.assertFalse(hasattr(Dict, "__missing__")) self.assertFalse(hasattr(self._make_dict({}), "__missing__")) # Test several cases: # (D) subclass defines __missing__ method returning a value # (E) subclass defines __missing__ method raising RuntimeError # (F) subclass sets __missing__ instance variable (no effect) # (G) subclass doesn't define __missing__ at a all - class D(dict): + class D(Dict): def __missing__(self, key): return 42 d = D({1: 2, 3: 4}) @@ -472,7 +485,7 @@ self.assertNotIn(2, d.keys()) self.assertEqual(d[2], 42) - class E(dict): + class E(Dict): def __missing__(self, key): raise RuntimeError(key) e = E() @@ -480,7 +493,7 @@ e[42] self.assertEqual(c.exception.args, (42,)) - class F(dict): + class F(Dict): def __init__(self): # An instance variable __missing__ should have no effect self.__missing__ = lambda key: None @@ -489,7 +502,7 @@ f[42] self.assertEqual(c.exception.args, (42,)) - class G(dict): + class G(Dict): pass g = G() with self.assertRaises(KeyError) as c: @@ -581,13 +594,15 @@ def test_container_iterator(self): # Bug #3680: tp_traverse was not implemented for dictiter objects + Dict = self.type2test class C(object): pass - iterators = (dict.iteritems, dict.itervalues, dict.iterkeys) + #iterators = (lambda c: c.iteritems, lambda c: c.itervalues, lambda c: c.iterkeys) + iterators = (Dict.iteritems, Dict.itervalues, Dict.iterkeys) for i in iterators: obj = C() ref = weakref.ref(obj) - container = {obj: 1} + container = self._make_dict({obj: 1}) obj.x = i(container) del obj, container gc.collect() @@ -694,11 +709,6 @@ pass self._tracked(MyDict()) - def test_list_equality(self): - class A(dict): pass - for dtype in (A, dict): - self.assertEquals([dtype()], [dict()]) - from test import mapping_tests diff --git a/Lib/test/test_dict_jy.py b/Lib/test/test_dict_jy.py --- a/Lib/test/test_dict_jy.py +++ b/Lib/test/test_dict_jy.py @@ -1,10 +1,12 @@ from test import test_support import unittest +import UserDict from collections import defaultdict import test_dict from java.util import HashMap, LinkedHashMap, Hashtable from java.util.concurrent import ConcurrentHashMap +from org.python.core import PyStringMap as stringmap class DictInitTest(unittest.TestCase): @@ -249,29 +251,26 @@ class JavaDictTest(test_dict.DictTest): + # Extend Python standard tests for dict. (Also used for Map proxies.) - _class = dict + type2test = dict def test_copy_java_hashtable(self): x = Hashtable() xc = x.copy() self.assertEqual(type(x), type(xc)) - def test_fromkeys(self): - super(JavaDictTest, self).test_fromkeys() - self.assertEqual(self._class.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) - def test_repr_value_None(self): - x = self._class({1:None}) + x = self.type2test({1:None}) self.assertEqual(repr(x), '{1: None}') def test_set_return_None(self): - x = self._class({1:2}) + x = self.type2test({1:2}) self.assertEqual(x.__setitem__(1, 3), None) self.assertEqual(x.__getitem__(1), 3) def test_del_return_None(self): - x = self._class({1:2}) + x = self.type2test({1:2}) self.assertEqual(x.__delitem__(1), None) self.assertEqual(len(x), 0) @@ -288,6 +287,13 @@ with self.assertRaises(AssertionError): prop(self._make_dict(a), b) + def test_list_equality(self): + class A(dict): pass + d = {'a':1, u'\xe7':2, u'\U00010842':3, 42:None} + for dtype in (dict, self.type2test, A): + self.assertEquals([dtype()], [dict()]) + self.assertEquals([dtype(d)], [d]) + # Some variants with unicode keys def test_repr_unicode(self): @@ -304,9 +310,10 @@ self.assertEqual(repr(d), "{u'\\uc6d4': {...}}") def test_fromkeys_unicode(self): - super(JavaDictTest, self).test_fromkeys() - self.assertEqual(self._class.fromkeys(u'\U00010840\U00010841\U00010842'), - {u'\U00010840':None, u'\U00010841':None, u'\U00010842':None}) + self.assertEqual(self.type2test.fromkeys(u'\U00010840\U00010841\U00010842', u'\u1810'), + {u'\U00010840':u'\u1810', u'\U00010841':u'\u1810', u'\U00010842':u'\u1810'}) + self.assertEqual(self.type2test.fromkeys(u'\U00010840\U00010841\U00010842'), + {u'\U00010840':None, u'\U00010841':None, u'\U00010842':None}) # NOTE: when comparing dictionaries below exclusively in Java # space, keys like 1 and 1L are different objects. Only when they @@ -347,54 +354,190 @@ self.assertGreater({1L: 2L, 3L: 4L}, self._make_dict({1: 2})) +class NullAcceptingDictTest(JavaDictTest): + # Extension of Java Map proxy tests to cases where the underlying + # container is able to accept nulls. Same tests as for dict (mostly). + + def test_missing(self): + # Proxy map types are not expected to support __missing__. + self.assertFalse(hasattr(self.type2test, "__missing__")) + self.assertFalse(hasattr(self._make_dict({}), "__missing__")) + + def test_fromkeys(self): + # Adapted from test_dict.DictTest.test_fromkeys by removal of test + # sub-classes since this does not work with proxy types. + Dict = self.type2test + + self.assertEqual(Dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + + d = self._make_dict({}) + self.assertIsNot(d.fromkeys('abc'), d) + self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0}) + self.assertEqual(d.fromkeys([]), {}) + def g(): + yield 1 + self.assertEqual(d.fromkeys(g()), {1:None}) + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3) + + class Exc(Exception): pass + class BadSeq(object): + def __iter__(self): + return self + def next(self): + raise Exc() + self.assertRaises(Exc, Dict.fromkeys, BadSeq()) + +class NullRejectingDictTest(NullAcceptingDictTest): + # Adaptation of Java Map proxy tests to cases where the underlying + # container cannot accept nulls, therefore None cannot be stored. + + def test_reject_none(self): + d = self._make_dict({'a': 1}) + with self.assertRaises(ValueError): + d['a'] = None + with self.assertRaises(ValueError): + d['b'] = None + # There is no __init__ or __new__ we can customise, so raises NullPointerException. + # self.assertRaises(ValueError, self._make_dict, {'c': None}) + self.assertRaises(ValueError, d.update, {'c': None}) + with self.assertRaises(ValueError): + d.update(c=None) + self.assertRaises(ValueError, d.fromkeys, 'cde') + self.assertRaises(ValueError, d.fromkeys, 'cde', None) + + def test_list_equality(self): + class A(dict): pass + d = {'a':1, u'\xe7':2, u'\U00010842':3, 42:True} + for dtype in (dict, self.type2test, A): + self.assertEquals([dtype()], [dict()]) + self.assertEquals([dtype(d)], [d]) + + @unittest.skip("not relevant since cannot hold None.") + def test_repr_value_None(self): pass + + def test_fromkeys(self): + # Adapted from test_dict.DictTest.test_fromkeys avoiding None + # (except as test) and by removal of test sub-classing. + Dict = self.type2test + + self.assertEqual(Dict.fromkeys('abc', 42), {'a':42, 'b':42, 'c':42}) + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3, 42) + self.assertRaises(ValueError, self._make_dict({}).fromkeys, 'abc', None) + + d = self._make_dict({}) + self.assertIsNot(d.fromkeys('abc', 42), d) + self.assertEqual(d.fromkeys('abc', 42), {'a':42, 'b':42, 'c':42}) + self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0}) + self.assertEqual(d.fromkeys([], 42), {}) + def g(): + yield 1 + self.assertEqual(d.fromkeys(g(), 42), {1:42}) + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3) + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3, 42) + + class Exc(Exception): pass + class BadSeq(object): + def __iter__(self): + return self + def next(self): + raise Exc() + self.assertRaises(Exc, Dict.fromkeys, BadSeq()) + + def test_fromkeys_unicode(self): + self.assertEqual(self.type2test.fromkeys(u'\U00010840\U00010841\U00010842', u'\u1810'), + {u'\U00010840':u'\u1810', u'\U00010841':u'\u1810', u'\U00010842':u'\u1810'}) + + def test_setdefault(self): + # Adapted from test_dict.DictTest.test_setdefault avoiding None + d = self._make_dict({'key0': False}) + d.setdefault('key0', []) + self.assertIs(d.setdefault('key0'), False) + d.setdefault('key', []).append(3) + self.assertEqual(d['key'][0], 3) + d.setdefault('key', []).append(4) + self.assertEqual(len(d['key']), 2) + self.assertRaises(TypeError, d.setdefault) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.setdefault, x, []) + + @unittest.skip("See bjo #2746. Java keys() returns an Enumerator.") + def test_has_key(self): pass # defining here only so we can skip it + + @unittest.skip("See bjo #2746. Java keys() returns an Enumerator.") + def test_keys(self): pass # defining here only so we can skip it + + class PyStringMapDictTest(test_dict.DictTest): # __dict__ for objects uses PyStringMap for historical reasons, so # we have to test separately - def _class(self, d): - # PyStringMap pretends to be a regular dict, so doing - # type(C().__dict__)() will not be helpful - it creates a - # regular dict. So explicitly create new objects and return - # their __dict__ - class C(object): - pass - newdict = C().__dict__ - newdict.update(d) - return newdict + type2test = stringmap + + def test_missing(self): + Dict = self.type2test + # Make sure dict doesn't have a __missing__ method + self.assertFalse(hasattr(Dict, "__missing__")) + self.assertFalse(hasattr(self._make_dict({}), "__missing__")) + # PyStringMap is not expected to support __missing__ as it cannot be sub-classed. + # At least, it wasn't added when it was added to PyDictionary. + + def test_fromkeys(self): + # Based on test_dict.DictTest.test_fromkeys, without sub-classing stringmap + Dict = self.type2test + + self.assertEqual(Dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + + d = self._make_dict({}) + self.assertIsNot(d.fromkeys('abc'), d) + self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None}) + self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0}) + self.assertEqual(d.fromkeys([]), {}) + def g(): + yield 1 + self.assertEqual(d.fromkeys(g()), {1:None}) + self.assertRaises(TypeError, self._make_dict({}).fromkeys, 3) + + class Exc(Exception): pass + + class BadSeq(object): + def __iter__(self): + return self + def next(self): + raise Exc() + + self.assertRaises(Exc, Dict.fromkeys, BadSeq()) + + # test fast path for dictionary inputs + d = Dict(zip(range(6), range(6))) + self.assertEqual(Dict.fromkeys(d, 0), Dict(zip(range(6), [0]*6))) -class JavaHashMapDictTest(JavaDictTest): - _class = HashMap -class JavaLinkedHashMapDictTest(JavaDictTest): - _class = LinkedHashMap - -class JavaHashtableDictTest(JavaDictTest): - _class = Hashtable - - @unittest.skip("FIXME: see bjo #2746") - def test_has_key(self): pass # defining here only so we can skip it - - @unittest.skip("FIXME: see bjo #2746") - def test_keys(self): pass # defining here only so we can skip it +class JavaHashMapDictTest(NullAcceptingDictTest): + type2test = HashMap - @unittest.skip("FIXME: see bjo #2746") - def test_repr_value_None(self): pass # defining here only so we can skip it - -class JavaConcurrentHashMapDictTest(JavaDictTest): - _class = ConcurrentHashMap - - @unittest.skip("FIXME: bjo #2711 affects ConcurrentHashMap in all Java versions.") - def test_setdefault_atomic(self): pass # defining here only so we can skip it +class JavaLinkedHashMapDictTest(NullAcceptingDictTest): + type2test = LinkedHashMap - @unittest.skip("FIXME: see bjo #2746") - def test_has_key(self): pass # defining here only so we can skip it +class JavaHashtableDictTest(NullRejectingDictTest): + type2test = Hashtable - @unittest.skip("FIXME: see bjo #2746") - def test_keys(self): pass # defining here only so we can skip it - - @unittest.skip("FIXME: see bjo #2746") - def test_repr_value_None(self): pass # defining here only so we can skip it +class JavaConcurrentHashMapDictTest(NullRejectingDictTest): + type2test = ConcurrentHashMap def test_main(): diff --git a/src/org/python/core/JavaProxyMap.java b/src/org/python/core/JavaProxyMap.java --- a/src/org/python/core/JavaProxyMap.java +++ b/src/org/python/core/JavaProxyMap.java @@ -1,5 +1,6 @@ package org.python.core; +import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -56,14 +57,14 @@ } // Loop through all entries checking the keys and values are matched for (Entry entry : selfMap.entrySet()) { - Object jkey = entry.getKey(); - Object jval = entry.getValue(); - PyObject oVal = oDict.__finditem__(Py.java2py(jkey)); + Object k = entry.getKey(); + Object v = entry.getValue(); + PyObject oVal = oDict.__finditem__(Py.java2py(k)); if (oVal == null) { // No value for this key in oDict, therefore not equal return Py.False; } - if (!Py.java2py(jval)._eq(oVal).__nonzero__()) { + if (!Py.java2py(v)._eq(oVal).__nonzero__()) { // The values for this key differ therefore not equal return Py.False; } @@ -75,9 +76,9 @@ Object oj = other.getJavaProxy(); if (oj instanceof Map) { // Being compared to a Java Map convert to Python - Map jMap = (Map) oj; + Map map = (Map) oj; final Map pyMap = new HashMap<>(); - for (Entry el : jMap.entrySet()) { + for (Entry el : map.entrySet()) { pyMap.put(Py.java2py(el.getKey()), Py.java2py(el.getValue())); } // Compare again this time after conversion to Python dict @@ -96,6 +97,34 @@ return object.getType().isSubType(PyDictionary.TYPE); } + /** + * Substitute for {@link Py#tojava(PyObject, Class)} when the second argument is + * {@code Object.class}, and in which we allow a {@code null} argument to signify {@code None}, + * since {@code null} is then the return value. + */ + private static Object tojava(PyObject pyo) { + return (pyo == null || pyo == Py.None) ? null : Py.tojava(pyo, Object.class); + } + + /** Return Python {@code ValueError} that None is not allowed. */ + private static RuntimeException nullException() { + return Py.ValueError( + "None is not allowed because underlying container cannot store a Java null."); + } + + /** Return Python {@code ValueError} that None is not allowed, or the {@code NullPointerException}, + * if in fact the value was not {@code None}. + * + * @param npe original exception + * @param key possibly causing the problem + * @param value possibly causing the problem + * @return the Python {@code ValueError} + */ + private static RuntimeException nullException(NullPointerException npe, Object key, + Object value) { + return (value == Py.None || value == Py.None) ? nullException() : npe; + } + /* * Map ordering comparisons (lt, le, gt, ge) are based on the key sets; we just define mapLe + * mapEq for total ordering of such key sets @@ -104,8 +133,8 @@ Set selfKeys = ((Map) self.getJavaProxy()).keySet(); if (other.getType().isSubType(PyDictionary.TYPE)) { PyDictionary oDict = (PyDictionary) other; - for (Object jkey : selfKeys) { - if (!oDict.__contains__(Py.java2py(jkey))) { + for (Object k : selfKeys) { + if (!oDict.__contains__(Py.java2py(k))) { return Py.False; } } @@ -113,8 +142,8 @@ } else { Object oj = other.getJavaProxy(); if (oj instanceof Map) { - Map oMap = (Map) oj; - return Py.newBoolean(oMap.keySet().containsAll(selfKeys)); + Map map = (Map) oj; + return Py.newBoolean(map.keySet().containsAll(selfKeys)); } else { return null; } @@ -207,8 +236,7 @@ private static final PyBuiltinMethodNarrow mapContainsProxy = new MapMethod("__contains__", 1) { @Override public PyObject __call__(PyObject obj) { - Object other = obj.__tojava__(Object.class); - return asMap().containsKey(other) ? Py.True : Py.False; + return asMap().containsKey(tojava(obj)) ? Py.True : Py.False; } }; /* @@ -225,61 +253,71 @@ @Override public PyObject __call__(PyObject key, PyObject _default) { - Object jkey = Py.tojava(key, Object.class); - if (asMap().containsKey(jkey)) { - return Py.java2py(asMap().get(jkey)); + Map map = asMap(); + Object k = tojava(key); + if (map.containsKey(k)) { + return Py.java2py(map.get(k)); } else { return _default; } } }; + private static final PyBuiltinMethodNarrow mapGetItemProxy = new MapMethod("__getitem__", 1) { @Override public PyObject __call__(PyObject key) { - Object jkey = Py.tojava(key, Object.class); - if (asMap().containsKey(jkey)) { - return Py.java2py(asMap().get(jkey)); - } else { - throw Py.KeyError(key); + Map map = asMap(); + Object k = tojava(key); + if (map.containsKey(k)) { + return Py.java2py(map.get(k)); } + throw Py.KeyError(key); } }; + private static final PyBuiltinMethodNarrow mapPutProxy = new MapMethod("__setitem__", 2) { @Override public PyObject __call__(PyObject key, PyObject value) { - asMap().put(Py.tojava(key, Object.class), - value == Py.None ? Py.None : Py.tojava(value, Object.class)); - return Py.None; + try { + asMap().put(tojava(key), tojava(value)); + return Py.None; + } catch (NullPointerException npe) { + throw nullException(npe, key, value); + } } }; + private static final PyBuiltinMethodNarrow mapRemoveProxy = new MapMethod("__delitem__", 1) { @Override public PyObject __call__(PyObject key) { - Object jkey = Py.tojava(key, Object.class); - if (asMap().remove(jkey) == null) { - throw Py.KeyError(key); + Map map = asMap(); + Object k = tojava(key); + if (map.containsKey(k)) { + map.remove(k); + return Py.None; } - return Py.None; + throw Py.KeyError(key); } }; + private static final PyBuiltinMethodNarrow mapIterItemsProxy = new MapMethod("iteritems", 0) { @Override public PyObject __call__() { - final Iterator> entrySetIterator = asMap().entrySet().iterator(); + final Iterator> entryIterator = asMap().entrySet().iterator(); return new PyIterator() { @Override public PyObject __iternext__() { - if (entrySetIterator.hasNext()) { - Map.Entry nextEntry = entrySetIterator.next(); + if (entryIterator.hasNext()) { + Map.Entry e = entryIterator.next(); // yield a Python tuple object (key, value) - return new PyTuple(Py.java2py(nextEntry.getKey()), - Py.java2py(nextEntry.getValue())); + return new PyTuple(Py.java2py(e.getKey()), Py.java2py(e.getValue())); } return null; } }; } }; + private static final PyBuiltinMethodNarrow mapIterKeysProxy = new MapMethod("iterkeys", 0) { @Override public PyObject __call__() { @@ -297,6 +335,7 @@ }; } }; + private static final PyBuiltinMethodNarrow mapIterValuesProxy = new MapMethod("itervalues", 0) { @Override public PyObject __call__() { @@ -314,12 +353,14 @@ }; } }; + private static final PyBuiltinMethodNarrow mapHasKeyProxy = new MapMethod("has_key", 1) { @Override public PyObject __call__(PyObject key) { - return asMap().containsKey(Py.tojava(key, Object.class)) ? Py.True : Py.False; + return asMap().containsKey(tojava(key)) ? Py.True : Py.False; } }; + private static final PyBuiltinMethodNarrow mapKeysProxy = new MapMethod("keys", 0) { @Override public PyObject __call__() { @@ -330,6 +371,7 @@ return keys; } }; + private static final PyBuiltinMethod mapValuesProxy = new MapMethod("values", 0) { @Override public PyObject __call__() { @@ -340,6 +382,7 @@ return values; } }; + private static final PyBuiltinMethodNarrow mapSetDefaultProxy = new MapMethod("setdefault", 1, 2) { @Override public PyObject __call__(PyObject key) { @@ -347,16 +390,22 @@ } @Override - public PyObject __call__(PyObject key, PyObject _default) { - Object jkey = Py.tojava(key, Object.class); - Object jval = asMap().get(jkey); - if (jval == null) { - asMap().put(jkey, _default == Py.None ? Py.None : Py.tojava(_default, Object.class)); - return _default; + public PyObject __call__(PyObject pykey, PyObject _default) { + Map map = asMap(); + Object key = tojava(pykey); + try { + if (map.containsKey(key)) { + return Py.java2py(map.get(key)); + } else { + map.put(key, tojava(_default)); + return _default; + } + } catch (NullPointerException npe) { + throw nullException(npe, key, _default); } - return Py.java2py(jval); } }; + private static final PyBuiltinMethodNarrow mapPopProxy = new MapMethod("pop", 1, 2) { @Override public PyObject __call__(PyObject key) { @@ -365,61 +414,67 @@ @Override public PyObject __call__(PyObject key, PyObject _default) { - Object jkey = Py.tojava(key, Object.class); - if (asMap().containsKey(jkey)) { - PyObject value = Py.java2py(asMap().remove(jkey)); - assert (value != null); - return Py.java2py(value); + Map map = asMap(); + Object k = tojava(key); + if (map.containsKey(k)) { + return Py.java2py(map.remove(k)); + } else if (_default == null) { + throw Py.KeyError(key); } else { - if (_default == null) { - throw Py.KeyError(key); - } return _default; } } }; + private static final PyBuiltinMethodNarrow mapPopItemProxy = new MapMethod("popitem", 0) { @Override public PyObject __call__() { - if (asMap().size() == 0) { - throw Py.KeyError("popitem(): map is empty"); + Map map = asMap(); + Iterator> entryIterator = map.entrySet().iterator(); + if (entryIterator.hasNext()) { + Map.Entry e = entryIterator.next(); + entryIterator.remove(); + return new PyTuple(Py.java2py(e.getKey()), Py.java2py(e.getValue())); } - Object key = asMap().keySet().toArray()[0]; - Object val = asMap().remove(key); - return Py.java2py(val); + throw Py.KeyError("popitem(): map is empty"); } }; + private static final PyBuiltinMethodNarrow mapItemsProxy = new MapMethod("items", 0) { @Override public PyObject __call__() { PyList items = new PyList(); for (Map.Entry entry : asMap().entrySet()) { - items.add(new PyTuple(Py.java2py(entry.getKey()), - Py.java2py(entry.getValue()))); + items.add(new PyTuple(Py.java2py(entry.getKey()), Py.java2py(entry.getValue()))); } return items; } }; + private static final PyBuiltinMethodNarrow mapCopyProxy = new MapMethod("copy", 0) { @Override public PyObject __call__() { - Map jmap = asMap(); - Map jclone; + Map map = asMap(); + Map newMap; + Class> clazz; try { - jclone = (Map) jmap.getClass().getDeclaredConstructor() - .newInstance(); + clazz = (Class>) map.getClass(); + Constructor> ctor = clazz.getDeclaredConstructor(); + newMap = ctor.newInstance(); + for (Map.Entry entry : map.entrySet()) { + newMap.put(entry.getKey(), entry.getValue()); + } + } catch (NullPointerException npe) { + throw nullException(); } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) { throw Py.JavaError(e); } - for (Map.Entry entry : jmap.entrySet()) { - jclone.put(entry.getKey(), entry.getValue()); - } - return Py.java2py(jclone); + return Py.java2py(newMap); } }; + private static final PyBuiltinMethodNarrow mapUpdateProxy = new MapMethod("update", 0, 1) { - private Map jmap; @Override public PyObject __call__() { @@ -437,53 +492,53 @@ @Override public PyObject __call__(PyObject[] args, String[] keywords) { - if ((args.length - keywords.length) != 1) { - throw info.unexpectedCall(args.length, false); + // Adapted from PyDictionary#update + int nargs = args.length - keywords.length; + if (nargs > 1) { + throw PyBuiltinCallable.DefaultInfo.unexpectedCall(nargs, false, "update", 0, 1); } - jmap = asMap(); - PyObject other = args[0]; - // update with entries from `other` (adapted from their equivalent in PyDictionary#update) - Object proxy = other.getJavaProxy(); - if (proxy instanceof Map) { - merge((Map) proxy); - } else if (other.__findattr__("keys") != null) { - merge(other); - } else { - mergeFromSeq(other); - } - // update with entries from keyword arguments - for (int i = 0; i < keywords.length; i++) { - String jkey = keywords[i]; - PyObject value = args[1 + i]; - jmap.put(jkey, Py.tojava(value, Object.class)); + Map map = asMap(); + try { + if (nargs == 1) { + PyObject other = args[0]; + Object proxy = other.getJavaProxy(); + if (proxy instanceof Map) { + // other proxies a Java container: take contents verbatim. + map.putAll((Map) proxy); + } else if (other instanceof PyDictionary) { + // keys and values must be converted from Python to Java equivalents. + mergeFromSeq(map, other.invoke("items")); + } else if (other instanceof PyStringMap) { + // keys and values must be converted from Python to Java equivalents. + mergeFromKeys(map, other, ((PyStringMap) other).keys()); + } else if (other.__findattr__("keys") != null) { + // This is a dict-like object but addressed by looking up the keys. + mergeFromKeys(map, other, other.invoke("keys")); + } else { + // This should be a sequence of tuples (each an entry). + mergeFromSeq(map, other); + } + } + // update with entries from keyword arguments + for (int i = 0; i < keywords.length; i++) { + String k = keywords[i]; + Object v = tojava(args[nargs + i]); + map.put(k, v); + } + } catch (NullPointerException npe) { + throw nullException(); } return Py.None; } - private void merge(Map other) { - for (Map.Entry entry : other.entrySet()) { - jmap.put(entry.getKey(), entry.getValue()); + private void mergeFromKeys(Map map, PyObject other, PyObject keys) { + for (PyObject key : keys.asIterable()) { + Object value = tojava(other.__getitem__(key)); + map.put(tojava(key), value); } } - private void merge(PyObject other) { - if (other instanceof PyDictionary) { - jmap.putAll(((PyDictionary) other).getMap()); - } else if (other instanceof PyStringMap) { - mergeFromKeys(other, ((PyStringMap) other).keys()); - } else { - mergeFromKeys(other, other.invoke("keys")); - } - } - - private void mergeFromKeys(PyObject other, PyObject keys) { - for (PyObject key : keys.asIterable()) { - jmap.put(Py.tojava(key, Object.class), - Py.tojava(other.__getitem__(key), Object.class)); - } - } - - private void mergeFromSeq(PyObject other) { + private void mergeFromSeq(Map map, PyObject other) { PyObject pairs = other.__iter__(); PyObject pair; @@ -502,11 +557,11 @@ throw Py.ValueError(String.format("dictionary update sequence element #%d " + "has length %d; 2 is required", i, n)); } - jmap.put(Py.tojava(pair.__getitem__(0), Object.class), - Py.tojava(pair.__getitem__(1), Object.class)); + map.put(tojava(pair.__getitem__(0)), tojava(pair.__getitem__(1))); } } }; + private static final PyBuiltinClassMethodNarrow mapFromKeysProxy = new MapClassMethod("fromkeys", 1, 2) { @Override public PyObject __call__(PyObject keys) { @@ -515,19 +570,18 @@ @Override public PyObject __call__(PyObject keys, PyObject _default) { - Object defobj = _default == null ? Py.None : Py.tojava(_default, Object.class); - Class theClass = asClass(); + Object defobj = tojava(_default); + Class> clazz; try { - /* - * always injected to java.util.Map, so we know the class object we get from - * asClass is subtype of java.util.Map - */ - Map theMap = (Map) theClass - .getDeclaredConstructor().newInstance(); + clazz = (Class>) asClass(); + Constructor> ctor = clazz.getDeclaredConstructor(); + Map theMap = ctor.newInstance(); for (PyObject key : keys.asIterable()) { - theMap.put(Py.tojava(key, Object.class), defobj); + theMap.put(tojava(key), defobj); } return Py.java2py(theMap); + } catch (NullPointerException npe) { + throw nullException(); } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) { throw Py.JavaError(e); -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Tue Apr 9 01:15:01 2019 From: jython-checkins at python.org (jeff.allen) Date: Tue, 09 Apr 2019 05:15:01 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Allow_struct_unpack_and_un?= =?utf-8?q?pack=5Ffrom_bytearray_and_buffer_=28GH-121=29?= Message-ID: <20190409051501.1.04EE16BDA2D312ED@mg.python.org> https://hg.python.org/jython/rev/66091cb89839 changeset: 8235:66091cb89839 user: Ray Ferguson date: Mon Apr 08 22:22:35 2019 +0100 summary: Allow struct unpack and unpack_from bytearray and buffer (GH-121) struct module from CPython 2.7.6 supports unpack from a bytearray or buffer. This patch adds support for unpack() and unpack_from() for the additional types and adds test cases based on tests/test_struct.py which is not run in regrtest by default. Modified by Jeff from https://github.com/jythontools/jython/pull/121 files: Lib/test/test_struct_jy.py | 158 +++++++++++++++ NEWS | 1 + src/org/python/core/Py2kBuffer.java | 7 +- src/org/python/core/PyByteArray.java | 11 +- src/org/python/modules/struct.java | 27 ++ 5 files changed, 200 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_struct_jy.py b/Lib/test/test_struct_jy.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_struct_jy.py @@ -0,0 +1,158 @@ +import unittest +from test import test_support +import struct + +import sys +ISBIGENDIAN = sys.byteorder == "big" + +class StructTests(unittest.TestCase): # (format, argument, big-endian result, little-endian result, asymmetric) + _tests = [ + ('c', 'a', 'a', 'a', 0), + ('xc', 'a', '\0a', '\0a', 0), + ('cx', 'a', 'a\0', 'a\0', 0), + ('s', 'a', 'a', 'a', 0), + ('0s', 'helloworld', '', '', 1), + ('1s', 'helloworld', 'h', 'h', 1), + ('9s', 'helloworld', 'helloworl', 'helloworl', 1), + ('10s', 'helloworld', 'helloworld', 'helloworld', 0), + ('11s', 'helloworld', 'helloworld\0', 'helloworld\0', 1), + ('20s', 'helloworld', 'helloworld'+10*'\0', 'helloworld'+10*'\0', 1), + ('b', 7, '\7', '\7', 0), + ('b', -7, '\371', '\371', 0), + ('B', 7, '\7', '\7', 0), + ('B', 249, '\371', '\371', 0), + ('h', 700, '\002\274', '\274\002', 0), + ('h', -700, '\375D', 'D\375', 0), + ('H', 700, '\002\274', '\274\002', 0), + ('H', 0x10000-700, '\375D', 'D\375', 0), + ('i', 70000000, '\004,\035\200', '\200\035,\004', 0), + ('i', -70000000, '\373\323\342\200', '\200\342\323\373', 0), + ('I', 70000000L, '\004,\035\200', '\200\035,\004', 0), + ('I', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), + ('l', 70000000, '\004,\035\200', '\200\035,\004', 0), + ('l', -70000000, '\373\323\342\200', '\200\342\323\373', 0), + ('L', 70000000L, '\004,\035\200', '\200\035,\004', 0), + ('L', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), + ('f', 2.0, '@\000\000\000', '\000\000\000@', 0), + ('d', 2.0, '@\000\000\000\000\000\000\000', + '\000\000\000\000\000\000\000@', 0), + ('f', -2.0, '\300\000\000\000', '\000\000\000\300', 0), + ('d', -2.0, '\300\000\000\000\000\000\000\000', + '\000\000\000\000\000\000\000\300', 0), + ] + + def test_struct(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack(xfmt, res)[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + + def test_struct_unpack_bytearray(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack(xfmt, bytearray(res))[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + + def test_struct_unpack_buffer(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack(xfmt, buffer(res))[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + def test_struct_unpack_from(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack_from(xfmt, res)[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + + def test_struct_unpack_from_bytearray(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack_from(xfmt, bytearray(res))[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + + def test_struct_unpack_from_buffer(self): + for fmt, arg, big, lil, asy in self._tests: + for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), + ('='+fmt, ISBIGENDIAN and big or lil)]: + res = struct.pack(xfmt, arg) + self.assertEqual(res,exp,msg="pack(%r, %r) -> %r # expected %r" % + (fmt, arg, res, exp)) + n=struct.calcsize(xfmt) + self.assertEqual(n, len(res),msg="calcsize(%r) -> %d # expected %d" % + (xfmt, n, len(res))) + rev = struct.unpack_from(xfmt, buffer(res))[0] + if asy: + self.assertNotEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, exp)) + else: + self.assertEqual(arg,rev,msg="unpack(%r, %r) -> (%r,) # expected (%r,)" % + (fmt, res, rev, arg)) + + + + +def test_main(): + test_support.run_unittest(__name__) + +if __name__ == "__main__": + test_main() diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ Development tip Bugs fixed + - [ GH-121 ] Allow struct unpack and unpack_from bytearray and buffer - [ 2635 ] AST.lineno ignored by compile - [ 2744 ] Support buffer type in marshal.dump(s) - [ 2077 ] marshal doesn't raise error when fed unmarshalable object diff --git a/src/org/python/core/Py2kBuffer.java b/src/org/python/core/Py2kBuffer.java --- a/src/org/python/core/Py2kBuffer.java +++ b/src/org/python/core/Py2kBuffer.java @@ -203,8 +203,13 @@ @ExposedMethod(doc = BuiltinDocs.buffer___str___doc) final PyString buffer___str__() { + return new PyString(toString()); + } + + @Override + public String toString() { try (PyBuffer buf = getBuffer()) { - return new PyString(buf.toString()); + return buf.toString(); } } diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -2014,12 +2014,17 @@ */ @Override public String toString() { - return bytearray_repr(); + return this.asString(); + } + + @Override + public PyString __repr__(){ + return bytearray___repr__(); } @ExposedMethod(names = {"__repr__"}, doc = BuiltinDocs.bytearray___repr___doc) - final synchronized String bytearray_repr() { - return basebytes_repr("bytearray(b", ")"); + final synchronized PyString bytearray___repr__() { + return new PyString(basebytes_repr("bytearray(b", ")")); } /** diff --git a/src/org/python/modules/struct.java b/src/org/python/modules/struct.java --- a/src/org/python/modules/struct.java +++ b/src/org/python/modules/struct.java @@ -21,6 +21,8 @@ import java.math.BigInteger; import org.python.core.ClassDictInit; import org.python.core.PyArray; +import org.python.core.PyByteArray; +import org.python.core.Py2kBuffer; /** * This module performs conversions between Python values and C @@ -1097,6 +1099,31 @@ return unpack(f, size, format, new ByteStream(string)); } + public static PyTuple unpack(String format, Py2kBuffer buffer){ + return unpack(format, buffer.toString()); + } + + public static PyTuple unpack(String format, PyByteArray bytearray) { + /* bytearray is added in 2.7.7 */ + return unpack(format, bytearray.toString()); + } + + public static PyTuple unpack_from(String format, Py2kBuffer buffer) { + return unpack_from(format, buffer.toString(), 0); + } + + public static PyTuple unpack_from(String format, PyByteArray bytearray) { + return unpack_from(format, bytearray.toString(), 0); + } + + public static PyTuple unpack_from(String format, Py2kBuffer buffer, int offset) { + return unpack_from(format, buffer.toString(), offset); + } + + public static PyTuple unpack_from(String format, PyByteArray bytearray, int offset) { + return unpack_from(format, bytearray.toString(), offset); + } + public static PyTuple unpack_from(String format, String string) { return unpack_from(format, string, 0); } -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Tue Apr 9 01:15:01 2019 From: jython-checkins at python.org (jeff.allen) Date: Tue, 09 Apr 2019 05:15:01 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_=28Trivial=29_coding_stand?= =?utf-8?q?ard_adjustments_and_copyright_notice_in_struct=2Ejava=2E?= Message-ID: <20190409051501.1.830015F8E30EE4BF@mg.python.org> https://hg.python.org/jython/rev/ead57cad60e6 changeset: 8236:ead57cad60e6 user: Jeff Allen date: Mon Apr 08 23:20:18 2019 +0100 summary: (Trivial) coding standard adjustments and copyright notice in struct.java. files: src/org/python/modules/struct.java | 508 +++++++++------- 1 files changed, 283 insertions(+), 225 deletions(-) diff --git a/src/org/python/modules/struct.java b/src/org/python/modules/struct.java --- a/src/org/python/modules/struct.java +++ b/src/org/python/modules/struct.java @@ -1,14 +1,15 @@ -/* - * Copyright 1999 Finn Bock. - * - * This program contains material copyrighted by: - * Copyright 1991-1995 by Stichting Mathematisch Centrum, Amsterdam, - * The Netherlands. - */ +// (c)2019 Jython Developers. Licensed to PSF under a Contributor Agreement. +// +// First Java version copyright 1999 Finn Bock also contains material Copyright 1991-1995 by +// Stichting Mathematisch Centrum, Amsterdam, The Netherlands. package org.python.modules; +import org.python.core.ClassDictInit; import org.python.core.Py; +import org.python.core.Py2kBuffer; +import org.python.core.PyArray; +import org.python.core.PyByteArray; import org.python.core.PyException; import org.python.core.PyFloat; import org.python.core.PyList; @@ -19,11 +20,9 @@ import org.python.core.PyTuple; import java.math.BigInteger; -import org.python.core.ClassDictInit; -import org.python.core.PyArray; -import org.python.core.PyByteArray; -import org.python.core.Py2kBuffer; + +//@formatter:off /** * This module performs conversions between Python values and C * structs represented as Python strings. It uses format strings @@ -255,14 +254,13 @@ * @author Finn Bock, bckfnn at pipmail.dknet.dk * @version struct.java,v 1.6 1999/04/17 12:04:34 fb Exp */ +//@formatter:on public class struct implements ClassDictInit { - /** - * Exception raised on various occasions; argument is a - * string describing what is wrong. - */ + /** Exception raised on various occasions; argument is a string describing what is wrong. */ public static final PyObject error = Py.makeClass("error", Py.Exception, exceptionNamespace()); + //@formatter:off public static String __doc__ = "Functions to convert between Python values and C structs.\n" + "Python strings are used to hold the data representing the C\n" + @@ -287,9 +285,10 @@ "Whitespace between formats is ignored.\n" + "\n" + "The variable struct.error is an exception raised on errors."; - + //@formatter:on static class FormatDef { + char name; int size; int alignment; @@ -301,28 +300,30 @@ return this; } - void pack(ByteStream buf, PyObject value) {} + void pack(ByteStream buf, PyObject value) {} Object unpack(ByteStream buf) { return null; } int doPack(ByteStream buf, int count, int pos, PyObject[] args) { - if (pos + count > args.length) + if (pos + count > args.length) { throw StructError("insufficient arguments to pack"); + } int cnt = count; - while (count-- > 0) + while (count-- > 0) { pack(buf, args[pos++]); + } return cnt; } void doUnpack(ByteStream buf, int count, PyList list) { - while (count-- > 0) + while (count-- > 0) { list.append(Py.java2py(unpack(buf))); + } } - int get_int(PyObject value) { try { return value.asInt(); @@ -332,42 +333,43 @@ } long get_long(PyObject value) { - if (value instanceof PyLong){ + if (value instanceof PyLong) { Object v = value.__tojava__(Long.TYPE); if (v == Py.NoConversion) { throw StructError("long int too long to convert"); } return ((Long) v).longValue(); - } else + } else { return get_int(value); + } } BigInteger get_ulong(PyObject value) { - if (value instanceof PyLong){ - BigInteger v = (BigInteger)value.__tojava__(BigInteger.class); - if (v.compareTo(PyLong.MAX_ULONG) > 0){ + if (value instanceof PyLong) { + BigInteger v = (BigInteger) value.__tojava__(BigInteger.class); + if (v.compareTo(PyLong.MAX_ULONG) > 0) { throw StructError("unsigned long int too long to convert"); } return v; - } else + } else { return BigInteger.valueOf(get_int(value)); + } } double get_float(PyObject value) { return value.asDouble(); } - void BEwriteInt(ByteStream buf, int v) { buf.writeByte((v >>> 24) & 0xFF); buf.writeByte((v >>> 16) & 0xFF); - buf.writeByte((v >>> 8) & 0xFF); - buf.writeByte((v >>> 0) & 0xFF); + buf.writeByte((v >>> 8) & 0xFF); + buf.writeByte((v >>> 0) & 0xFF); } void LEwriteInt(ByteStream buf, int v) { - buf.writeByte((v >>> 0) & 0xFF); - buf.writeByte((v >>> 8) & 0xFF); + buf.writeByte((v >>> 0) & 0xFF); + buf.writeByte((v >>> 8) & 0xFF); buf.writeByte((v >>> 16) & 0xFF); buf.writeByte((v >>> 24) & 0xFF); } @@ -389,8 +391,8 @@ } } + static class ByteStream { - static class ByteStream { char[] data; int len; int pos; @@ -404,15 +406,16 @@ ByteStream(String s) { this(s, 0); } - + ByteStream(String s, int offset) { int size = s.length() - offset; data = new char[size]; s.getChars(offset, s.length(), data, 0); len = size; pos = 0; - -// System.out.println("s.length()=" + s.length() + ",offset=" + offset + ",size=" + size + ",data=" + Arrays.toString(data)); + +// System.out.println("s.length()=" + s.length() + ",offset=" + offset + ",size=" + size + ",data=" +// + Arrays.toString(data)); } int readByte() { @@ -424,14 +427,12 @@ this.pos += len; } - String readString(int l) { char[] data = new char[l]; read(data, 0, l); return new String(data); } - private void ensureCapacity(int l) { if (pos + l >= data.length) { char[] b = new char[(pos + l) * 2]; @@ -440,13 +441,11 @@ } } - void writeByte(int b) { ensureCapacity(1); - data[pos++] = (char)(b & 0xFF); + data[pos++] = (char) (b & 0xFF); } - void write(char[] buf, int pos, int len) { ensureCapacity(len); System.arraycopy(buf, pos, data, this.pos, len); @@ -459,7 +458,6 @@ write(data, 0, len); } - int skip(int l) { pos += l; return pos; @@ -469,317 +467,370 @@ return pos; } + @Override public String toString() { return new String(data, 0, pos); } } + static class PadFormatDef extends FormatDef { - static class PadFormatDef extends FormatDef { + @Override int doPack(ByteStream buf, int count, int pos, PyObject[] args) { - while (count-- > 0) + while (count-- > 0) { buf.writeByte(0); + } return 0; } + @Override void doUnpack(ByteStream buf, int count, PyList list) { - while (count-- > 0) + while (count-- > 0) { buf.readByte(); + } } } + static class StringFormatDef extends FormatDef { - static class StringFormatDef extends FormatDef { + @Override int doPack(ByteStream buf, int count, int pos, PyObject[] args) { PyObject value = args[pos]; - if (!(value instanceof PyString)) + if (!(value instanceof PyString)) { throw StructError("argument for 's' must be a string"); + } String s = value.toString(); int len = s.length(); buf.writeString(s, 0, Math.min(count, len)); if (len < count) { count -= len; - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) { buf.writeByte(0); + } } return 1; } + @Override void doUnpack(ByteStream buf, int count, PyList list) { list.append(Py.newString(buf.readString(count))); } } + static class PascalStringFormatDef extends StringFormatDef { - static class PascalStringFormatDef extends StringFormatDef { + @Override int doPack(ByteStream buf, int count, int pos, PyObject[] args) { PyObject value = args[pos]; - if (!(value instanceof PyString)) + if (!(value instanceof PyString)) { throw StructError("argument for 'p' must be a string"); + } - buf.writeByte(Math.min(0xFF, Math.min(value.toString().length(), count-1))); - return super.doPack(buf, count-1, pos, args); + buf.writeByte(Math.min(0xFF, Math.min(value.toString().length(), count - 1))); + return super.doPack(buf, count - 1, pos, args); } + @Override void doUnpack(ByteStream buf, int count, PyList list) { int n = buf.readByte(); - if (n >= count) - n = count-1; + if (n >= count) { + n = count - 1; + } super.doUnpack(buf, n, list); - buf.skip(Math.max(count-n-1, 0)); + buf.skip(Math.max(count - n - 1, 0)); } } + static class CharFormatDef extends FormatDef { - static class CharFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - if (!(value instanceof PyString) || value.__len__() != 1) + if (!(value instanceof PyString) || value.__len__() != 1) { throw StructError("char format require string of length 1"); + } buf.writeByte(value.toString().charAt(0)); } + @Override Object unpack(ByteStream buf) { - return Py.newString((char)buf.readByte()); + return Py.newString((char) buf.readByte()); } } + static class ByteFormatDef extends FormatDef { - static class ByteFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { buf.writeByte(get_int(value)); } + @Override Object unpack(ByteStream buf) { int b = buf.readByte(); - if (b > Byte.MAX_VALUE) + if (b > Byte.MAX_VALUE) { b -= 0x100; + } return Py.newInteger(b); } } static class UnsignedByteFormatDef extends ByteFormatDef { + + @Override Object unpack(ByteStream buf) { return Py.newInteger(buf.readByte()); } } - + static class PointerFormatDef extends FormatDef { + FormatDef init(char name) { String dataModel = System.getProperty("sun.arch.data.model"); - if (dataModel == null) + if (dataModel == null) { throw Py.NotImplementedError("Can't determine if JVM is 32- or 64-bit"); + } int length = dataModel.equals("64") ? 8 : 4; super.init(name, length, length); return this; } - + + @Override void pack(ByteStream buf, PyObject value) { throw Py.NotImplementedError("Pointer packing/unpacking not implemented in Jython"); } + @Override Object unpack(ByteStream buf) { throw Py.NotImplementedError("Pointer packing/unpacking not implemented in Jython"); } } - + static class LEShortFormatDef extends FormatDef { + + @Override void pack(ByteStream buf, PyObject value) { int v = get_int(value); buf.writeByte(v & 0xFF); buf.writeByte((v >> 8) & 0xFF); } + @Override Object unpack(ByteStream buf) { - int v = buf.readByte() | - (buf.readByte() << 8); - if (v > Short.MAX_VALUE) - v -= 0x10000 ; + int v = buf.readByte() | (buf.readByte() << 8); + if (v > Short.MAX_VALUE) { + v -= 0x10000; + } return Py.newInteger(v); } } static class LEUnsignedShortFormatDef extends LEShortFormatDef { + + @Override Object unpack(ByteStream buf) { - int v = buf.readByte() | - (buf.readByte() << 8); + int v = buf.readByte() | (buf.readByte() << 8); return Py.newInteger(v); } } + static class BEShortFormatDef extends FormatDef { - static class BEShortFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { int v = get_int(value); buf.writeByte((v >> 8) & 0xFF); buf.writeByte(v & 0xFF); } + @Override Object unpack(ByteStream buf) { - int v = (buf.readByte() << 8) | - buf.readByte(); - if (v > Short.MAX_VALUE) + int v = (buf.readByte() << 8) | buf.readByte(); + if (v > Short.MAX_VALUE) { v -= 0x10000; + } return Py.newInteger(v); } } + static class BEUnsignedShortFormatDef extends BEShortFormatDef { - static class BEUnsignedShortFormatDef extends BEShortFormatDef { + @Override Object unpack(ByteStream buf) { - int v = (buf.readByte() << 8) | - buf.readByte(); + int v = (buf.readByte() << 8) | buf.readByte(); return Py.newInteger(v); } } + static class LEIntFormatDef extends FormatDef { - static class LEIntFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { LEwriteInt(buf, get_int(value)); } + @Override Object unpack(ByteStream buf) { int v = LEreadInt(buf); return Py.newInteger(v); } } + static class LEUnsignedIntFormatDef extends FormatDef { - static class LEUnsignedIntFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - LEwriteInt(buf, (int)(get_long(value) & 0xFFFFFFFF)); + LEwriteInt(buf, (int) (get_long(value) & 0xFFFFFFFF)); } + @Override Object unpack(ByteStream buf) { long v = LEreadInt(buf); - if (v < 0) + if (v < 0) { v += 0x100000000L; + } return new PyLong(v); } } + static class BEIntFormatDef extends FormatDef { - static class BEIntFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { BEwriteInt(buf, get_int(value)); } + @Override Object unpack(ByteStream buf) { return Py.newInteger(BEreadInt(buf)); } } + static class BEUnsignedIntFormatDef extends FormatDef { - static class BEUnsignedIntFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - BEwriteInt(buf, (int)(get_long(value) & 0xFFFFFFFF)); + BEwriteInt(buf, (int) (get_long(value) & 0xFFFFFFFF)); } + + @Override Object unpack(ByteStream buf) { long v = BEreadInt(buf); - if (v < 0) + if (v < 0) { v += 0x100000000L; + } return new PyLong(v); } } static class LEUnsignedLongFormatDef extends FormatDef { + + @Override void pack(ByteStream buf, PyObject value) { BigInteger bi = get_ulong(value); if (bi.compareTo(BigInteger.valueOf(0)) < 0) { throw StructError("can't convert negative long to unsigned"); } long lvalue = bi.longValue(); // underflow is OK -- the bits are correct - int high = (int) ( (lvalue & 0xFFFFFFFF00000000L)>>32 ); - int low = (int) ( lvalue & 0x00000000FFFFFFFFL ); - LEwriteInt( buf, low ); - LEwriteInt( buf, high ); + int high = (int) ((lvalue & 0xFFFFFFFF00000000L) >> 32); + int low = (int) (lvalue & 0x00000000FFFFFFFFL); + LEwriteInt(buf, low); + LEwriteInt(buf, high); } + @Override Object unpack(ByteStream buf) { - long low = ( LEreadInt( buf ) & 0X00000000FFFFFFFFL ); - long high = ( LEreadInt( buf ) & 0X00000000FFFFFFFFL ); - java.math.BigInteger result=java.math.BigInteger.valueOf(high); - result=result.multiply(java.math.BigInteger.valueOf(0x100000000L)); - result=result.add(java.math.BigInteger.valueOf(low)); + long low = (LEreadInt(buf) & 0X00000000FFFFFFFFL); + long high = (LEreadInt(buf) & 0X00000000FFFFFFFFL); + java.math.BigInteger result = java.math.BigInteger.valueOf(high); + result = result.multiply(java.math.BigInteger.valueOf(0x100000000L)); + result = result.add(java.math.BigInteger.valueOf(low)); return new PyLong(result); } } + static class BEUnsignedLongFormatDef extends FormatDef { - static class BEUnsignedLongFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { BigInteger bi = get_ulong(value); if (bi.compareTo(BigInteger.valueOf(0)) < 0) { throw StructError("can't convert negative long to unsigned"); } long lvalue = bi.longValue(); // underflow is OK -- the bits are correct - int high = (int) ( (lvalue & 0xFFFFFFFF00000000L)>>32 ); - int low = (int) ( lvalue & 0x00000000FFFFFFFFL ); - BEwriteInt( buf, high ); - BEwriteInt( buf, low ); + int high = (int) ((lvalue & 0xFFFFFFFF00000000L) >> 32); + int low = (int) (lvalue & 0x00000000FFFFFFFFL); + BEwriteInt(buf, high); + BEwriteInt(buf, low); } + @Override Object unpack(ByteStream buf) { - long high = ( BEreadInt( buf ) & 0X00000000FFFFFFFFL ); - long low = ( BEreadInt( buf ) & 0X00000000FFFFFFFFL ); - java.math.BigInteger result=java.math.BigInteger.valueOf(high); - result=result.multiply(java.math.BigInteger.valueOf(0x100000000L)); - result=result.add(java.math.BigInteger.valueOf(low)); + long high = (BEreadInt(buf) & 0X00000000FFFFFFFFL); + long low = (BEreadInt(buf) & 0X00000000FFFFFFFFL); + java.math.BigInteger result = java.math.BigInteger.valueOf(high); + result = result.multiply(java.math.BigInteger.valueOf(0x100000000L)); + result = result.add(java.math.BigInteger.valueOf(low)); return new PyLong(result); } } + static class LELongFormatDef extends FormatDef { - static class LELongFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - long lvalue = get_long( value ); - int high = (int) ( (lvalue & 0xFFFFFFFF00000000L)>>32 ); - int low = (int) ( lvalue & 0x00000000FFFFFFFFL ); - LEwriteInt( buf, low ); - LEwriteInt( buf, high ); + long lvalue = get_long(value); + int high = (int) ((lvalue & 0xFFFFFFFF00000000L) >> 32); + int low = (int) (lvalue & 0x00000000FFFFFFFFL); + LEwriteInt(buf, low); + LEwriteInt(buf, high); } + @Override Object unpack(ByteStream buf) { long low = LEreadInt(buf) & 0x00000000FFFFFFFFL; - long high = ((long)(LEreadInt(buf))<<32) & 0xFFFFFFFF00000000L; - long result=(high|low); + long high = ((long) (LEreadInt(buf)) << 32) & 0xFFFFFFFF00000000L; + long result = (high | low); return new PyLong(result); } } + static class BELongFormatDef extends FormatDef { - static class BELongFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - long lvalue = get_long( value ); - int high = (int) ( (lvalue & 0xFFFFFFFF00000000L)>>32 ); - int low = (int) ( lvalue & 0x00000000FFFFFFFFL ); - BEwriteInt( buf, high ); - BEwriteInt( buf, low ); + long lvalue = get_long(value); + int high = (int) ((lvalue & 0xFFFFFFFF00000000L) >> 32); + int low = (int) (lvalue & 0x00000000FFFFFFFFL); + BEwriteInt(buf, high); + BEwriteInt(buf, low); } + @Override Object unpack(ByteStream buf) { - long high = ((long)(BEreadInt(buf))<<32) & 0xFFFFFFFF00000000L; + long high = ((long) (BEreadInt(buf)) << 32) & 0xFFFFFFFF00000000L; long low = BEreadInt(buf) & 0x00000000FFFFFFFFL; - long result=(high|low); + long result = (high | low); return new PyLong(result); } } + static class LEFloatFormatDef extends FormatDef { - static class LEFloatFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - int bits = Float.floatToIntBits((float)get_float(value)); + int bits = Float.floatToIntBits((float) get_float(value)); LEwriteInt(buf, bits); } + @Override Object unpack(ByteStream buf) { int bits = LEreadInt(buf); float v = Float.intBitsToFloat(bits); - if (PyFloat.float_format == PyFloat.Format.UNKNOWN && ( - Float.isInfinite(v) || Float.isNaN(v))) { + if (PyFloat.float_format == PyFloat.Format.UNKNOWN + && (Float.isInfinite(v) || Float.isNaN(v))) { throw Py.ValueError("can't unpack IEEE 754 special value on non-IEEE platform"); } return Py.newFloat(v); @@ -787,36 +838,40 @@ } static class LEDoubleFormatDef extends FormatDef { + + @Override void pack(ByteStream buf, PyObject value) { long bits = Double.doubleToLongBits(get_float(value)); - LEwriteInt(buf, (int)(bits & 0xFFFFFFFF)); - LEwriteInt(buf, (int)(bits >>> 32)); + LEwriteInt(buf, (int) (bits & 0xFFFFFFFF)); + LEwriteInt(buf, (int) (bits >>> 32)); } + @Override Object unpack(ByteStream buf) { - long bits = (LEreadInt(buf) & 0xFFFFFFFFL) + - (((long)LEreadInt(buf)) << 32); + long bits = (LEreadInt(buf) & 0xFFFFFFFFL) + (((long) LEreadInt(buf)) << 32); double v = Double.longBitsToDouble(bits); - if (PyFloat.double_format == PyFloat.Format.UNKNOWN && - (Double.isInfinite(v) || Double.isNaN(v))) { + if (PyFloat.double_format == PyFloat.Format.UNKNOWN + && (Double.isInfinite(v) || Double.isNaN(v))) { throw Py.ValueError("can't unpack IEEE 754 special value on non-IEEE platform"); } return Py.newFloat(v); } } + static class BEFloatFormatDef extends FormatDef { - static class BEFloatFormatDef extends FormatDef { + @Override void pack(ByteStream buf, PyObject value) { - int bits = Float.floatToIntBits((float)get_float(value)); + int bits = Float.floatToIntBits((float) get_float(value)); BEwriteInt(buf, bits); } + @Override Object unpack(ByteStream buf) { int bits = BEreadInt(buf); float v = Float.intBitsToFloat(bits); - if (PyFloat.float_format == PyFloat.Format.UNKNOWN && ( - Float.isInfinite(v) || Float.isNaN(v))) { + if (PyFloat.float_format == PyFloat.Format.UNKNOWN + && (Float.isInfinite(v) || Float.isNaN(v))) { throw Py.ValueError("can't unpack IEEE 754 special value on non-IEEE platform"); } return Py.newFloat(v); @@ -824,25 +879,27 @@ } static class BEDoubleFormatDef extends FormatDef { + + @Override void pack(ByteStream buf, PyObject value) { long bits = Double.doubleToLongBits(get_float(value)); - BEwriteInt(buf, (int)(bits >>> 32)); - BEwriteInt(buf, (int)(bits & 0xFFFFFFFF)); + BEwriteInt(buf, (int) (bits >>> 32)); + BEwriteInt(buf, (int) (bits & 0xFFFFFFFF)); } + @Override Object unpack(ByteStream buf) { - long bits = (((long) BEreadInt(buf)) << 32) + - (BEreadInt(buf) & 0xFFFFFFFFL); + long bits = (((long) BEreadInt(buf)) << 32) + (BEreadInt(buf) & 0xFFFFFFFFL); double v = Double.longBitsToDouble(bits); - if (PyFloat.double_format == PyFloat.Format.UNKNOWN && - (Double.isInfinite(v) || Double.isNaN(v))) { + if (PyFloat.double_format == PyFloat.Format.UNKNOWN + && (Double.isInfinite(v) || Double.isNaN(v))) { throw Py.ValueError("can't unpack IEEE 754 special value on non-IEEE platform"); } return Py.newFloat(v); } } - + //@formatter:off private static FormatDef[] lilendian_table = { new PadFormatDef() .init('x', 1, 0), new ByteFormatDef() .init('b', 1, 0), @@ -900,70 +957,66 @@ new BEDoubleFormatDef() .init('d', 8, 8), new PointerFormatDef() .init('P') }; - - + //@formatter:on static FormatDef[] whichtable(String pfmt) { char c = pfmt.charAt(0); switch (c) { - case '<' : - return lilendian_table; - case '>': - case '!': - // Network byte order is big-endian - return bigendian_table; - case '=': - return bigendian_table; - case '@': - default: - return native_table; + case '<': + return lilendian_table; + case '>': + case '!': + // Network byte order is big-endian + return bigendian_table; + case '=': + return bigendian_table; + case '@': + default: + return native_table; } } - private static FormatDef getentry(char c, FormatDef[] f) { for (int i = 0; i < f.length; i++) { - if (f[i].name == c) + if (f[i].name == c) { return f[i]; + } } throw StructError("bad char in struct format"); } - - private static int align(int size, FormatDef e) { if (e.alignment != 0) { - size = ((size + e.alignment - 1) - / e.alignment) - * e.alignment; + size = ((size + e.alignment - 1) / e.alignment) * e.alignment; } return size; } - - static int calcsize(String format, FormatDef[] f) { int size = 0; int len = format.length(); for (int j = 0; j < len; j++) { char c = format.charAt(j); - if (j == 0 && (c=='@' || c=='<' || c=='>' || c=='=' || c=='!')) + if (j == 0 && (c == '@' || c == '<' || c == '>' || c == '=' || c == '!')) { continue; - if (Character.isWhitespace(c)) + } + if (Character.isWhitespace(c)) { continue; + } int num = 1; if (Character.isDigit(c)) { num = Character.digit(c, 10); - while (++j < len && - Character.isDigit((c = format.charAt(j)))) { - int x = num*10 + Character.digit(c, 10); - if (x/10 != num) + while (++j < len && Character.isDigit((c = format.charAt(j)))) { + int x = num * 10 + Character.digit(c, 10); + if (x / 10 != num) { throw StructError("overflow in item count"); + } num = x; } - if (j >= len) + if (j >= len) { break; + } } FormatDef e = getentry(c, f); @@ -972,69 +1025,70 @@ size = align(size, e); int x = num * itemsize; size += x; - if (x/itemsize != num || size < 0) + if (x / itemsize != num || size < 0) { throw StructError("total struct size too long"); + } } return size; } - /** - * Return the size of the struct (and hence of the string) - * corresponding to the given format. + * Return the size of the struct (and hence of the string) corresponding to the given format. */ static public int calcsize(String format) { FormatDef[] f = whichtable(format); return calcsize(format, f); } - /** - * Return a string containing the values v1, v2, ... packed according - * to the given format. The arguments must match the - * values required by the format exactly. + * Return a string containing the values v1, v2, ... packed according to the given format. The + * arguments must match the values required by the format exactly. */ static public PyString pack(PyObject[] args) { - if (args.length < 1) + if (args.length < 1) { Py.TypeError("illegal argument type for built-in operation"); + } String format = args[0].toString(); FormatDef[] f = whichtable(format); int size = calcsize(format, f); - + return new PyString(pack(format, f, size, 1, args).toString()); } - + // xxx - may need to consider doing a generic arg parser here static public void pack_into(PyObject[] args) { - if (args.length < 3) + if (args.length < 3) { Py.TypeError("illegal argument type for built-in operation"); + } String format = args[0].toString(); FormatDef[] f = whichtable(format); int size = calcsize(format, f); pack_into(format, f, size, 1, args); } - + static void pack_into(String format, FormatDef[] f, int size, int argstart, PyObject[] args) { - if (args.length - argstart < 2) + if (args.length - argstart < 2) { Py.TypeError("illegal argument type for built-in operation"); + } if (!(args[argstart] instanceof PyArray)) { throw Py.TypeError("pack_into takes an array arg"); // as well as a buffer, what else? } - PyArray buffer = (PyArray)args[argstart]; + PyArray buffer = (PyArray) args[argstart]; int offset = args[argstart + 1].asInt(); ByteStream res = pack(format, f, size, argstart + 2, args); if (res.pos > buffer.__len__()) { - throw StructError("pack_into requires a buffer of at least " + res.pos + " bytes, got " + buffer.__len__()); + throw StructError("pack_into requires a buffer of at least " + res.pos + " bytes, got " + + buffer.__len__()); } for (int i = 0; i < res.pos; i++, offset++) { char val = res.data[i]; buffer.set(offset, val); } } - + static ByteStream pack(String format, FormatDef[] f, int size, int start, PyObject[] args) { ByteStream res = new ByteStream(); @@ -1042,64 +1096,68 @@ int len = format.length(); for (int j = 0; j < len; j++) { char c = format.charAt(j); - if (j == 0 && (c=='@' || c=='<' || c=='>' || c=='=' || c=='!')) + if (j == 0 && (c == '@' || c == '<' || c == '>' || c == '=' || c == '!')) { continue; - if (Character.isWhitespace(c)) + } + if (Character.isWhitespace(c)) { continue; + } int num = 1; if (Character.isDigit(c)) { num = Character.digit(c, 10); - while (++j < len && Character.isDigit((c = format.charAt(j)))) - num = num*10 + Character.digit(c, 10); - if (j >= len) + while (++j < len && Character.isDigit((c = format.charAt(j)))) { + num = num * 10 + Character.digit(c, 10); + } + if (j >= len) { break; + } } FormatDef e = getentry(c, f); // Fill pad bytes with zeros int nres = align(res.size(), e) - res.size(); - while (nres-- > 0) + while (nres-- > 0) { res.writeByte(0); + } i += e.doPack(res, num, i, args); } - if (i < args.length) + if (i < args.length) { throw StructError("too many arguments for pack format"); + } return res; } - + /** + * Unpack the string (presumably packed by pack(fmt, ...)) according to the given format. The + * result is a tuple even if it contains exactly one item. The string must contain exactly the + * amount of data required by the format (i.e. len(string) must equal calcsize(fmt)). + */ - /** - * Unpack the string (presumably packed by pack(fmt, ...)) according - * to the given format. The result is a tuple even if it contains - * exactly one item. - * The string must contain exactly the amount of data required by - * the format (i.e. len(string) must equal calcsize(fmt)). - */ - public static PyTuple unpack(String format, String string) { FormatDef[] f = whichtable(format); int size = calcsize(format, f); int len = string.length(); - if (size != len) + if (size != len) { throw StructError("unpack str size does not match format"); - return unpack(f, size, format, new ByteStream(string)); + } + return unpack(f, size, format, new ByteStream(string)); } - + public static PyTuple unpack(String format, PyArray buffer) { String string = buffer.tostring(); FormatDef[] f = whichtable(format); int size = calcsize(format, f); int len = string.length(); - if (size != len) + if (size != len) { throw StructError("unpack str size does not match format"); - return unpack(f, size, format, new ByteStream(string)); + } + return unpack(f, size, format, new ByteStream(string)); } - - public static PyTuple unpack(String format, Py2kBuffer buffer){ + + public static PyTuple unpack(String format, Py2kBuffer buffer) { return unpack(format, buffer.toString()); } @@ -1125,47 +1183,48 @@ } public static PyTuple unpack_from(String format, String string) { - return unpack_from(format, string, 0); + return unpack_from(format, string, 0); } - + public static PyTuple unpack_from(String format, String string, int offset) { FormatDef[] f = whichtable(format); int size = calcsize(format, f); int len = string.length(); - if (size >= (len - offset + 1)) + if (size >= (len - offset + 1)) { throw StructError("unpack_from str size does not match format"); + } return unpack(f, size, format, new ByteStream(string, offset)); } - + static PyTuple unpack(FormatDef[] f, int size, String format, ByteStream str) { PyList res = new PyList(); int flen = format.length(); for (int j = 0; j < flen; j++) { char c = format.charAt(j); - if (j == 0 && (c=='@' || c=='<' || c=='>' || c=='=' || c=='!')) + if (j == 0 && (c == '@' || c == '<' || c == '>' || c == '=' || c == '!')) { continue; - if (Character.isWhitespace(c)) + } + if (Character.isWhitespace(c)) { continue; + } int num = 1; if (Character.isDigit(c)) { num = Character.digit(c, 10); - while (++j < flen && - Character.isDigit((c = format.charAt(j)))) - num = num*10 + Character.digit(c, 10); - if (j > flen) + while (++j < flen && Character.isDigit((c = format.charAt(j)))) { + num = num * 10 + Character.digit(c, 10); + } + if (j > flen) { break; + } } FormatDef e = getentry(c, f); - str.skip(align(str.size(), e) - str.size()); - e.doUnpack(str, num, res); } return PyTuple.fromIterable(res); } - static PyException StructError(String explanation) { return new PyException(error, explanation); } @@ -1175,10 +1234,9 @@ dict.__setitem__("__module__", new PyString("struct")); return dict; } - + public static void classDictInit(PyObject dict) { dict.__setitem__("Struct", PyStruct.TYPE); } } - -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Fri Apr 19 13:32:03 2019 From: jython-checkins at python.org (jeff.allen) Date: Fri, 19 Apr 2019 17:32:03 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Comments_and_minor_re-writ?= =?utf-8?q?e_of_PyJavaType=2EhandleMroError?= Message-ID: <20190419173203.1.D785E1D7A35C34E3@mg.python.org> https://hg.python.org/jython/rev/262428b2c51a changeset: 8237:262428b2c51a user: Jeff Allen date: Fri Apr 19 17:43:45 2019 +0100 summary: Comments and minor re-write of PyJavaType.handleMroError This is work in aid of bjo #2445, to make the logic clearer, without (I think) changing it. It adds (but skips) a failing test that reproduces the continuing problem with IBM classes related to MQ. files: Lib/test/test_java_integration.py | 11 +- src/org/python/core/Py.java | 39 +- src/org/python/core/PyJavaType.java | 141 +++++---- src/org/python/core/PyType.java | 140 +++++++-- tests/java/org/python/tests/mro/EclipseChallengeMRO.java | 29 +- tests/java/org/python/tests/mro/IBMMQChallengeMRO.java | 93 ++++++ 6 files changed, 309 insertions(+), 144 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -45,6 +45,7 @@ from javatests.ProxyTests import NullToString, Person from clamp import SerializableProxies +from unittest.case import skip @@ -575,9 +576,15 @@ self.assertEqual(set(m), set(["abc", "xyz"])) self.assertEqual(m["abc"], 42) - def test_diamond_lattice_inheritance(self): + def test_mro_eclipse(self): # http://bugs.jython.org/issue2445 - from org.python.tests.mro import TortureMRO + from org.python.tests.mro import EclipseChallengeMRO + + @unittest.skip("FIXME: see http://bugs.jython.org/issue2445") + def test_mro_ibmmq(self): + # http://bugs.jython.org/issue2445 + from org.python.tests.mro import IBMMQChallengeMRO + t = IBMMQChallengeMRO.mq.jms.MQQueue def roundtrip_serialization(obj): diff --git a/src/org/python/core/Py.java b/src/org/python/core/Py.java --- a/src/org/python/core/Py.java +++ b/src/org/python/core/Py.java @@ -69,8 +69,7 @@ /** A zero-length array of Strings to pass to functions that don't have any keyword arguments **/ public final static String[] NoKeywords = new String[0]; - /** A zero-length array of PyObject's to pass to functions that - expect zero-arguments **/ + /** A zero-length array of PyObject's to pass to functions when we have no arguments **/ public final static PyObject[] EmptyObjects = new PyObject[0]; /** A frozenset with zero elements **/ public final static PyFrozenSet EmptyFrozenSet = new PyFrozenSet(); @@ -2566,29 +2565,29 @@ } } + /** + * Turn any Python iterable into an array of its elements. + * + * @param iterable to evaluate + * @return array of elements from iterable + */ static PyObject[] make_array(PyObject iterable) { // Special-case the common tuple and list cases, for efficiency if (iterable instanceof PySequenceList) { return ((PySequenceList) iterable).getArray(); + } else { + int n = 10; + if (!(iterable instanceof PyGenerator)) { + try { + n = iterable.__len__(); // may be available, otherwise ... + } catch (PyException pye) { /* ... leave n at 0 */ } + } + List objs = new ArrayList(n); + for (PyObject item : iterable.asIterable()) { + objs.add(item); + } + return objs.toArray(Py.EmptyObjects); } - - // Guess result size and allocate space. The typical make_array arg supports - // __len__, with one exception being generators, so avoid the overhead of an - // exception from __len__ in their case - int n = 10; - if (!(iterable instanceof PyGenerator)) { - try { - n = iterable.__len__(); - } catch (PyException pye) { - // ok - } - } - - List objs = new ArrayList(n); - for (PyObject item : iterable.asIterable()) { - objs.add(item); - } - return objs.toArray(Py.EmptyObjects); } //------------------------constructor-section--------------------------- diff --git a/src/org/python/core/PyJavaType.java b/src/org/python/core/PyJavaType.java --- a/src/org/python/core/PyJavaType.java +++ b/src/org/python/core/PyJavaType.java @@ -1,5 +1,8 @@ package org.python.core; +import org.python.core.util.StringUtil; +import org.python.util.Generic; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,7 +30,9 @@ import java.util.Enumeration; import java.util.EventListener; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -35,9 +40,6 @@ import java.util.Set; import java.util.Stack; -import org.python.core.util.StringUtil; -import org.python.util.Generic; - public class PyJavaType extends PyType { private final static Class[] OO = {PyObject.class, PyObject.class}; @@ -218,76 +220,88 @@ postDelattr(name); } + /** + * {@inheritDoc} + *

+ * An override specifically for Java classes (that are not {@link PyObject}) has the possibility + * of completing the MRO in {@code mro}, by additional steps affecting the {@code mro} and + * {@code toMerge} passed in. This divergence from the Python rules is acceptable for Java. + */ @Override void handleMroError(MROMergeState[] toMerge, List mro) { + if (underlying_class != null) { - // If this descends from PyObject, don't do the Java mro cleanup + // This descends from PyObject (but is not exposed): don't attempt recovery. super.handleMroError(toMerge, mro); } - Set inConflict = Generic.set(); - PyJavaType winner = null; + + // Make a set of all the PyJavaTypes still in the lists to merge. + Set inConflict = new LinkedHashSet<>(); for (MROMergeState mergee : toMerge) { for (int i = mergee.next; i < mergee.mro.length; i++) { - if (mergee.mro[i] == PyObject.TYPE - || mergee.mro[i] == PyType.fromClass(Object.class)) { - continue; + PyObject m = mergee.mro[i]; + if (m instanceof PyJavaType && m != Constant.OBJECT) { + inConflict.add((PyJavaType) m); } - if (winner == null) { - /* - * Pick an arbitrary class to be added to the mro next and break the conflict. - * If method name conflicts were allowed between methods added to Java types, it - * would go first, but that's prevented, so being a winner doesn't actually get - * it anything. - */ - winner = (PyJavaType) mergee.mro[i]; - } - inConflict.add((PyJavaType) mergee.mro[i]); } } - Set allModified = Generic.set(); - PyJavaType[] conflictedAttributes = inConflict.toArray(new PyJavaType[inConflict.size()]); - for (PyJavaType type : conflictedAttributes) { - if (type.modified == null) { - continue; - } - for (String method : type.modified) { - if (!allModified.add(method)) { - // Another type in conflict has this method, possibly fail - PyList types = new PyList(); - Set> proxySet = Generic.set(); - for (PyJavaType othertype : conflictedAttributes) { + /* + * Collect the names of all the methods added to any of these types (with certain + * exclusions) that occur in more than one of these residual types. If a name is found in + * more than one of these types, raise an error. + */ + Set allModified = new HashSet<>(); + for (PyJavaType type : inConflict) { + if (type.modified != null) { + // For every method name modified in type ... + for (String method : type.modified) { + if (!allModified.add(method)) { /* - * Ignore any pairings of types that are in a superclass/superinterface - * relationship with each other. This problem is a false positive that - * happens because of the automatic addition of methods so that Java classes - * behave more like their corresponding Python types, such as adding sort or - * remove. See http://bugs.jython.org/issue2445 + * The method name was already in the set, so has appeared already. Work out + * which one that was by rescanning. + */// XXX Why didn't we keep a map? + List types = new ArrayList<>(); + Set> proxySet = new HashSet<>(); + Class proxyType = type.getProxyType(); + for (PyJavaType othertype : inConflict) { + /* + * Ignore any pairings of types that are in a superclass/superinterface + * relationship with each other. This problem is a false positive that + * happens because of the automatic addition of methods so that Java + * classes behave more like their corresponding Python types, such as + * adding sort or remove. See http://bugs.jython.org/issue2445 + */ + if (othertype.modified != null && othertype.modified.contains(method)) { + Class otherProxyType = othertype.getProxyType(); + if (otherProxyType.isAssignableFrom(proxyType)) { + continue; + } else if (proxyType.isAssignableFrom(otherProxyType)) { + continue; + } else { + types.add(othertype); + proxySet.add(otherProxyType); + } + } + } + + /* + * Need to special case collections that implement both Iterable and Map. + * Ignore the conflict in having duplicate __iter__ added (see + * getCollectionProxies), while still allowing each path on the inheritance + * hierarchy to get an __iter__. Annoying but necessary logic. See + * http://bugs.jython.org/issue1878 */ - if (othertype.modified != null && othertype.modified.contains(method) - && !(othertype.getProxyType().isAssignableFrom(type.getProxyType()) - || type.getProxyType() - .isAssignableFrom(othertype.getProxyType()))) { - types.add(othertype); - proxySet.add(othertype.getProxyType()); + if (method.equals("__iter__") + && Generic.set(Iterable.class, Map.class).containsAll(proxySet)) { + continue; } - } - /* - * Need to special case collections that implement both Iterable and Map. Ignore - * the conflict in having duplicate __iter__ added (see getCollectionProxies), - * while still allowing each path on the inheritance hierarchy to get an - * __iter__. Annoying but necessary logic. See http://bugs.jython.org/issue1878 - */ - if (method.equals("__iter__") - && Generic.set(Iterable.class, Map.class).containsAll(proxySet)) { - continue; - } - if (types.size() > 0) { - throw Py.TypeError(String.format( - "Supertypes that share a modified attribute " - + "have an MRO conflict[attribute=%s, supertypes=%s, type=%s]", - method, types, this.getName())); + String fmt = "Supertypes that share a modified attribute " + + "have an MRO conflict[attribute=%s, supertypes=%s, type=%s]"; + if (types.size() > 0) { + throw Py.TypeError(String.format(fmt, method, types, this.getName())); + } } } } @@ -297,7 +311,7 @@ * We can keep trucking, there aren't any existing method name conflicts. Mark the conflicts * in all the classes so further method additions can check for trouble. */ - for (PyJavaType type : conflictedAttributes) { + for (PyJavaType type : inConflict) { for (PyJavaType otherType : inConflict) { if (otherType != type) { if (type.conflicted == null) { @@ -308,11 +322,18 @@ } } - // Add our winner to the mro, clear the clog, and try to finish the rest + /* + * Emit the first conflicting type we encountered to the MRO, and remove it from the working + * lists. Forcing a step like this is ok for classes compiled from Java as the order of + * bases is not significant as long as hierarchy is preserved. + */ + PyJavaType winner = inConflict.iterator().next(); mro.add(winner); for (MROMergeState mergee : toMerge) { mergee.removeFromUnmerged(winner); } + + // Restart the MRO generation algorithm from the current state. computeMro(toMerge, mro); } diff --git a/src/org/python/core/PyType.java b/src/org/python/core/PyType.java --- a/src/org/python/core/PyType.java +++ b/src/org/python/core/PyType.java @@ -1441,30 +1441,39 @@ return (tp_flags & Py.TPFLAGS_IS_ABSTRACT) != 0; } + /** + * Set the {@link #mro} field from the Python {@code mro()} method which uses + * ({@link #computeMro()} by default. We must repeat this whenever the bases of this type + * change, which they may in general for classes defined in Python. + */ private void mro_internal() { + if (getType() == TYPE) { - mro = computeMro(); + mro = computeMro(); // Shortcut + } else { + // Use the mro() method, which may have been redefined to find the MRO as an array. PyObject mroDescr = getType().lookup("mro"); if (mroDescr == null) { throw Py.AttributeError("mro"); } PyObject[] result = Py.make_array(mroDescr.__get__(null, getType()).__call__(this)); + // Verify that Python types in the MRO have a "solid base" in common with this type. PyType solid = solid_base(this); + for (PyObject cls : result) { if (cls instanceof PyClass) { continue; - } - if (!(cls instanceof PyType)) { - throw Py.TypeError(String.format("mro() returned a non-class ('%.500s')", - cls.getType().fastGetName())); - } - PyType t = (PyType) cls; - if (!solid.isSubType(solid_base(t))) { - throw Py.TypeError(String.format( - "mro() returned base with unsuitable layout " + "('%.500s')", - t.fastGetName())); + } else if (cls instanceof PyType) { + PyType t = (PyType) cls; + if (!solid.isSubType(solid_base(t))) { + String fmt = "mro() returned base with unsuitable layout ('%.500s')"; + throw Py.TypeError(String.format(fmt, t.fastGetName())); + } + } else { + String fmt = "mro() returned a non-class ('%.500s')"; + throw Py.TypeError(String.format(fmt, cls.getType().fastGetName())); } } mro = result; @@ -1605,14 +1614,20 @@ } @ExposedMethod(defaults = "null", doc = BuiltinDocs.type_mro_doc) - final PyList type_mro(PyObject o) { - if (o == null) { - return new PyList(computeMro()); - } - return new PyList(((PyType) o).computeMro()); + final PyList type_mro(PyObject X) { + // This is either X.mro (where X is a type object) or type.mro(X) + PyObject[] res = (X == null) ? computeMro() : ((PyType) X).computeMro(); + return new PyList(res); } + /** + * Examine the bases (which must contain no repetition) and the MROs of these bases and return + * the MRO of this class. + * + * @return the MRO of this class + */ PyObject[] computeMro() { + // First check that there are no duplicates amongst the bases of this class. for (int i = 0; i < bases.length; i++) { PyObject cur = bases[i]; for (int j = i + 1; j < bases.length; j++) { @@ -1624,6 +1639,7 @@ } } + // Build a table of the MROs of the bases as MROMergeState objects. MROMergeState[] toMerge = new MROMergeState[bases.length + 1]; for (int i = 0; i < bases.length; i++) { toMerge[i] = new MROMergeState(); @@ -1633,14 +1649,27 @@ toMerge[i].mro = classic_mro((PyClass) bases[i]); } } + + // Append to this table the list of bases of this class toMerge[bases.length] = new MROMergeState(); toMerge[bases.length].mro = bases; + // The head of the output MRO is the current class itself. List mro = Generic.list(); mro.add(this); + + // Now execute the core of the MRO generation algorithm. return computeMro(toMerge, mro); } + /** + * Core algorithm for computing the MRO for "new-style" (although it's been a while) Python + * classes. + * + * @param toMerge data structure representing the (partly processed) MROs of bases. + * @param mro partial MRO (initially only this class) + * @return the MRO of this class + */ PyObject[] computeMro(MROMergeState[] toMerge, List mro) { boolean addedProxy = false; Class thisProxyAttr = this.getProxyType(); @@ -1693,12 +1722,17 @@ } /** - * Must either throw an exception, or bring the merges in toMerge to completion by - * finishing filling in mro. + * This method is called when the {@link #computeMro(MROMergeState[], List)} reaches an impasse + * as far as its official algorithm is concerned, with the partial MRO and current state of the + * working lists at the point the problem is detected. The base implementation raises a Python + * {@code TypeError}, diagnosing the problem. + * + * @param toMerge partially processed algorithm state + * @param mro output MRO (incomplete) */ void handleMroError(MROMergeState[] toMerge, List mro) { StringBuilder msg = new StringBuilder( - "Cannot create a consistent method resolution\n" + "order (MRO) for bases "); + "Cannot create a consistent method resolution\norder (MRO) for bases "); Set set = Generic.set(); for (MROMergeState mergee : toMerge) { if (!mergee.isMerged()) { @@ -1720,7 +1754,9 @@ } /** - * Finds the parent of type with an underlying_class or with slots sans a __dict__ slot. + * Finds the first super-type of the given {@code type} that is a "solid base" of the type, that + * is, the returned type has an {@link #underlying_class}, or it defines {@code __slots__} and + * no instance level dictionary ({@code __dict__} attribute). */ private static PyType solid_base(PyType type) { do { @@ -1732,39 +1768,49 @@ return PyObject.TYPE; } + /** + * A "solid base" is a type that has an {@link #underlying_class}, or defines {@code __slots__} + * and no instance level dictionary (no {@code __dict__} attribute). + */ private static boolean isSolidBase(PyType type) { return type.underlying_class != null || (type.ownSlots != 0 && !type.needs_userdict); } /** - * Finds the base in bases with the most derived solid_base, ie the most base type + * Find the base selected from a {@link #bases} array that has the "most derived solid base". + * This will become the {@link #base} attribute of a class under construction, or in which the + * {@link #bases} tuple is being updated. On a successful return, the return value is one of the + * elements of the argument {@code bases} and the solid base of that return is a sub-type of the + * solid bases of all other (non-classic) elements of the argument. * * @throws Py.TypeError if the bases don't all derive from the same solid_base * @throws Py.TypeError if at least one of the bases isn't a new-style class */ private static PyType best_base(PyObject[] bases) { - PyType winner = null; - PyType candidate = null; - PyType best = null; - for (PyObject base : bases) { - if (base instanceof PyClass) { + PyType best = null; // The best base found so far + PyType bestSolid = null; // The solid base of the best base so far + for (PyObject b : bases) { + if (b instanceof PyType) { + PyType base = (PyType) b, solid = solid_base(base); + if (bestSolid == null) { + // First (non-classic) base we find becomes the best base so far + best = base; + bestSolid = solid; + } else if (bestSolid.isSubType(solid)) { + // Current best is still the best so far + } else if (solid.isSubType(bestSolid)) { + // base is better than the previous best since its solid base is more derived. + best = base; + bestSolid = solid; + } else { + throw Py.TypeError("multiple bases have instance lay-out conflict"); + } + } else if (b instanceof PyClass) { + // Skip over classic bases continue; - } - if (!(base instanceof PyType)) { + } else { throw Py.TypeError("bases must be types"); } - candidate = solid_base((PyType) base); - if (winner == null) { - winner = candidate; - best = (PyType) base; - } else if (winner.isSubType(candidate)) { - ; - } else if (candidate.isSubType(winner)) { - winner = candidate; - best = (PyType) base; - } else { - throw Py.TypeError("multiple bases have instance lay-out conflict"); - } } if (best == null) { throw Py.TypeError("a new-style class can't have only classic bases"); @@ -2603,6 +2649,20 @@ } mro = newMro.toArray(new PyObject[newMro.size()]); } + + @Override + public String toString() { + List names = Generic.list(); + for (int i = next; i < mro.length; i++) { + PyObject t = mro[i]; + if (t instanceof PyType) { + names.add(((PyType) t).name); + } else { + names.add(t.toString()); + } + } + return names.toString(); + } } /** diff --git a/tests/java/org/python/tests/mro/TortureMRO.java b/tests/java/org/python/tests/mro/EclipseChallengeMRO.java rename from tests/java/org/python/tests/mro/TortureMRO.java rename to tests/java/org/python/tests/mro/EclipseChallengeMRO.java --- a/tests/java/org/python/tests/mro/TortureMRO.java +++ b/tests/java/org/python/tests/mro/EclipseChallengeMRO.java @@ -1,4 +1,4 @@ -// Copyright (c) Jython Developers. +// Copyright (c)2019 Jython Developers. // Licensed to the Python Software Foundation under a Contributor Agreement. package org.python.tests.mro; @@ -6,46 +6,31 @@ /** * A class providing interface and abstract class relationships that approximate the structure of * org.eclipse.emf.ecore.util.DelegatingFeatureMap, in order to exercise b.j.o issue 2445. The - * complex inheritance (more complex than diamond inheritance) confused PyJavaType handling of the - * MRO. This class is imported by - * {@code test_java_integration.JavaMROTest.test_diamond_lattice_inheritance} as a test. + * complex inheritance confused PyJavaType handling of the MRO. This class is imported by + * {@code test_java_integration.JavaMROTest.test_mro_eclipse} as a test. *

- * An invocation at the prompt (for debugging use), and output before the fix, is: - * - *

- * PS > dist\bin\jython -S -c"from org.python.tests.mro import TortureMRO"
+ * An invocation at the prompt (for debugging use), and output before the fix, is: 
+ * PS > dist\bin\jython -S -c"from org.python.tests.mro import EclipseChallengeMRO"
  * Traceback (most recent call last):
  *   File "<string>", line 1, in <module>
  * TypeError: Supertypes that share a modified attribute have an MRO
  * conflict[attribute=sort, supertypes=[<type 'java.util.List'>,
- * <type 'java.util.AbstractList'>], type=TortureMRO$Target]
+ * <type 'java.util.AbstractList'>], type=EclipseChallengeMRO$Target]
  * 
*/ -public class TortureMRO { +public class EclipseChallengeMRO { interface Entry {} // Was FeatureMap.Entry - interface Thing {} // Was EStructuralFeature.Setting - interface EList extends java.util.List {} - interface IEList extends EList {} - interface FList extends EList {} // Was FeatureMap - interface FIEListThing extends FList, IEList, Thing {} // Was 2 FeatureMap.Internal - abstract static class AbstractEList extends java.util.AbstractList implements EList {} - abstract static class DEList extends AbstractEList {} - interface NList extends EList {} - abstract static class DNListImpl extends DEList implements NList {} - abstract static class DNIEListImpl extends DNListImpl implements IEList {} - abstract static class DNIEListThing extends DNIEListImpl implements Thing {} - public abstract static class Target extends DNIEListThing implements FIEListThing {} } diff --git a/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java b/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java new file mode 100644 --- /dev/null +++ b/tests/java/org/python/tests/mro/IBMMQChallengeMRO.java @@ -0,0 +1,93 @@ +// Copyright (c)2019 Jython Developers. +// Licensed to the Python Software Foundation under a Contributor Agreement. + +package org.python.tests.mro; + +import java.io.Serializable; +import java.util.Map; + +/** + * A class providing interface and abstract class relationships that approximate the structure of + * com.ibm classes related to MQ, in order to exercise b.j.o issue 2445. The complex inheritance + * confused PyJavaType handling of the MRO. This class is imported by + * {@code test_java_integration.JavaMROTest.test_mro_ibmmq}. + *

+ * An invocation at the prompt (for debugging use), and output before the fix, is:

+ * PS > dist\bin\jython -S -c "from org.python.tests.mro import IBMMQChallengeMRO; t=m.mq.jms.MQQueue"
+ * Traceback (most recent call last):
+ *   File "<string>", line 1, in <module>
+ * TypeError: Supertypes that share a modified attribute have an MRO conflict
+ * [attribute=get,supertypes=[<type
+ * 'org.python.tests.mro.IBMMQChallengeMRO$msg$client$jms$internal$JmsPropertyContextImpl'>],
+ * type=IBMMQChallengeMRO$mq$jms$MQQueue]
+ * 
+ */ +public class IBMMQChallengeMRO { + + static class javax_jms { // represents javax.jms + + public interface Queue extends Destination {} + public interface Destination {} + } + + static class javax_naming { // represents javax.naming + + public interface Referenceable {} + } + + static class msg { // represents com.ibm.msg + + static class client { // represents com.ibm.msg.client + + static class jms { // represents com.ibm.msg.client.jms + + public interface JmsDestination extends JmsPropertyContext, javax_jms.Destination {} + public interface JmsPropertyContext + extends JmsReadablePropertyContext, Map {} + public interface JmsReadablePropertyContext extends Serializable {} + public interface JmsQueue extends JmsDestination, javax_jms.Queue {} + + static class admin { // represents com.ibm.msg.client.jms.admin + + public static abstract/* ? */ class JmsJndiDestinationImpl + extends JmsDestinationImpl + implements JmsDestination, javax_naming.Referenceable, Serializable {} + public static abstract/* ? */ class JmsDestinationImpl + extends internal.JmsPropertyContextImpl implements JmsDestination {} + } + + static class internal { // represents com.ibm.msg.client.jms.internal + + public static abstract/* ? */ class JmsPropertyContextImpl + extends JmsReadablePropertyContextImpl implements JmsPropertyContext, + provider.ProviderPropertyContextCallback {} + public static abstract class JmsReadablePropertyContextImpl + implements JmsReadablePropertyContext {} + } + } + + static class provider { // represents com.ibm.msg.client.provider + + public interface ProviderPropertyContextCallback {} + } + } + } + + static class jms { // represents com.ibm.jms + + public interface JMSDestination extends javax_jms.Destination {} + } + + public static class mq { // represents com.ibm.mq + + public static class jms { // represents com.ibm.mq.jms + + public abstract/* ? */ class MQDestination + extends msg.client.jms.admin.JmsJndiDestinationImpl implements + javax_jms.Destination, IBMMQChallengeMRO.jms.JMSDestination, Serializable {} + /** Target class in the test **/ + public abstract/* ? */ class MQQueue extends MQDestination implements javax_jms.Queue, + msg.client.jms.JmsQueue, javax_naming.Referenceable, Serializable {} + } + } +} -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Apr 28 12:03:43 2019 From: jython-checkins at python.org (jeff.allen) Date: Sun, 28 Apr 2019 16:03:43 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Return_Java_super-class_to?= =?utf-8?q?_first_position_in_type=2E=5F=5Fbases=5F=5F=2E_Fixes_bjo_=23244?= =?utf-8?q?5=2E?= Message-ID: <20190428160343.1.ECF5F93B877A87D5@mg.python.org> https://hg.python.org/jython/rev/36db7416b3b7 changeset: 8239:36db7416b3b7 user: Jeff Allen date: Sun Apr 28 14:27:43 2019 +0100 summary: Return Java super-class to first position in type.__bases__. Fixes bjo #2445. The precedence of additions to the proxy types for java.util.List, etc. is sensitive to the MRO of proxies for Java classes implementing those interfaces. As initially implemented, this motivated moving the super-class to final position in type.__bases__, contrary to Java inheritance rles, and this caused problems in the proxies of classes with complex inheritance. The change here has us pay greater respect to Java inheritance, having found the placing last only to be necessary when the (nominal) super-class is java.lang.Object. This is done with minimal change to the apparatus of proxies and handleMroError, although close examination during the investigation suggested a number of improvements peripheral to the fix. files: Lib/test/test_java_integration.py | 25 +++---- NEWS | 2 +- src/org/python/core/Deriveds.java | 10 +- src/org/python/core/PyJavaType.java | 52 +++++++++++++--- 4 files changed, 58 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -557,18 +557,18 @@ self.assertRaises(TypeError, GetitemAdder.addPostdefined) def test_diamond_inheritance_of_iterable_and_map(self): - """Test deeply nested diamond inheritance of Iterable and Map, as see in some Clojure classes""" - # http://bugs.jython.org/issue1878 - from javatests import DiamondIterableMapMRO # this will raise a TypeError re MRO conflict without the fix - # Verify the correct MRO is generated - order is of course *important*; - # the following used types are implemented as empty interfaces/abstract classes, but match the inheritance graph - # and naming of Clojure/Storm. - # - # Also instead of directly importing, which would cause annoying bloat in javatests by making lots of little files, - # just match using str - this will still be stable/robust. - self.assertEqual( - str(DiamondIterableMapMRO.__mro__), - "(, , , , , , , , , , , , , , )") + """Test deeply nested diamond inheritance of Iterable and Map""" + # http://bugs.jython.org/issue1878. Previously raised a TypeError (MRO conflict). + from javatests import DiamondIterableMapMRO + # The MRO places Iterable ahead of Map (so that __iter__ means j.u.Iterator.__iter__). + # The following used types are empty interfaces and abstract classes matching the + # inheritance graph and naming of Clojure/Storm, where the bug was discovered. + # Match using str - this will still be stable/robust. + mrostr = str(DiamondIterableMapMRO.__mro__) + jli = mrostr.find("java.lang.Iterable") + self.assertGreater(jli, -1, "Iterable must be in the MRO") + jum = mrostr.find("java.util.Map") + self.assertGreater(jum, jli, "Map must come later than Iterable") # And usable with __iter__ and map functionality m = DiamondIterableMapMRO() m["abc"] = 42 @@ -580,7 +580,6 @@ # http://bugs.jython.org/issue2445 from org.python.tests.mro import EclipseChallengeMRO - @unittest.skip("FIXME: see http://bugs.jython.org/issue2445") def test_mro_ibmmq(self): # http://bugs.jython.org/issue2445 from org.python.tests.mro import IBMMQChallengeMRO diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -5,11 +5,11 @@ Development tip Bugs fixed + - [ 2445 ] Eclipse's DelegatingFeatureMap has MRO conflict (and IBM's MQQueue) - [ GH-121 ] Allow struct unpack and unpack_from bytearray and buffer - [ 2635 ] AST.lineno ignored by compile - [ 2744 ] Support buffer type in marshal.dump(s) - [ 2077 ] marshal doesn't raise error when fed unmarshalable object - - [ 2445 ] Eclipse's DelegatingFeatureMap has MRO conflict - [ 2732 ] Regression in large module support for pip - [ GH-131 ] Support Unicode in zxJDBC exceptions. - [ 2654 ] Imported modules allow access to submodules diff --git a/src/org/python/core/Deriveds.java b/src/org/python/core/Deriveds.java --- a/src/org/python/core/Deriveds.java +++ b/src/org/python/core/Deriveds.java @@ -1,4 +1,5 @@ -/* Copyright (c) Jython Developers */ +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; /** @@ -47,10 +48,9 @@ } else { PyObject getattribute = type.lookup("__getattribute__"); if (getattribute == null) { - // This shouldn't happen - throw Py.SystemError(String.format( - "__getattribute__ not found on type %s. See http://bugs.jython.org/issue2487 for details.", - type.getName())); + // This shouldn't happen (and isn't always to do with bjo #2487 when it does). + throw Py.SystemError( + String.format("__getattribute__ not found on type %s", type.getName())); } if (getattribute == objectGetattribute) { type.setUsesObjectGetattribute(true); diff --git a/src/org/python/core/PyJavaType.java b/src/org/python/core/PyJavaType.java --- a/src/org/python/core/PyJavaType.java +++ b/src/org/python/core/PyJavaType.java @@ -38,6 +38,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.Stack; @@ -262,7 +263,7 @@ /* * The method name was already in the set, so has appeared already. Work out * which one that was by rescanning. - */// XXX Why didn't we keep a map? + */ List types = new ArrayList<>(); Set> proxySet = new HashSet<>(); Class proxyType = type.getProxyType(); @@ -288,15 +289,27 @@ } /* - * Need to special case collections that implement both Iterable and Map. - * Ignore the conflict in having duplicate __iter__ added (see - * getCollectionProxies), while still allowing each path on the inheritance - * hierarchy to get an __iter__. Annoying but necessary logic. See - * http://bugs.jython.org/issue1878 + * Need to special case __iter__ in certain circumstances to ignore the + * conflict in having duplicate __iter__ added (see getCollectionProxies), + * while still allowing each path on the inheritance hierarchy to get an + * __iter__. Annoying but necessary logic. */ - if (method.equals("__iter__") - && Generic.set(Iterable.class, Map.class).containsAll(proxySet)) { - continue; + if (method.equals("__iter__")) { + if (Generic.set(Iterable.class, Map.class).containsAll(proxySet)) { + /* + * Need to special case __iter__ in collections that implement both + * Iterable and Map. See http://bugs.jython.org/issue1878 + */ + continue; + } else if (Generic.set(Iterator.class, Enumeration.class) + .containsAll(proxySet)) { + /* + * Need to special case __iter__ in iterators that Iterator and + * Enumeration. Annoying but necessary logic. See + * http://bugs.jython.org/issue2445 + */ + continue; + } } String fmt = "Supertypes that share a modified attribute " @@ -381,7 +394,7 @@ * superclass. */ needsInners.add(this); - List visibleBases = Generic.list(); + LinkedList visibleBases = new LinkedList<>(); for (Class iface : forClass.getInterfaces()) { if (iface == PyProxy.class || iface == ClassDictInit.class) { /* @@ -405,13 +418,27 @@ if (forClass == Object.class) { base = Constant.PYOBJECT; } else if (baseClass == null) { + /* + * It would be most like Java to have no base (like PyNone) but in that case, the + * MRO calculation puts Object ahead of the interface. Our patching of Java + * container interfaces to behave like Python container objects requires the + * opposite. + */ base = Constant.OBJECT; } else if (forClass == Class.class) { base = Constant.PYTYPE; } else { base = fromClass(baseClass); } - visibleBases.add(base); + + if (baseClass == null) { + // Object, an interface, a primitive or void: base goes last. + visibleBases.add(base); + } else { + // forClass represents a (concrete or abstract) class: base comes before interfaces. + visibleBases.push(base); + } + this.bases = visibleBases.toArray(new PyObject[visibleBases.size()]); mro = computeMro(); } @@ -467,10 +494,11 @@ } } } + // Methods must be in resolution order. See issue bjo #2391 for detail. Arrays.sort(methods, new MethodComparator(new ClassComparator())); - /* Add methods, also accumulating them in reflectedFuncs, and spotting Java Bean members. */ + // Add methods, also accumulating them in reflectedFuncs, and spotting Java Bean members. ArrayList reflectedFuncs = new ArrayList<>(methods.length); Map props = Generic.map(); Map> events = Generic.map(); -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Sun Apr 28 12:03:43 2019 From: jython-checkins at python.org (jeff.allen) Date: Sun, 28 Apr 2019 16:03:43 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_=28Trivial=29_documentatio?= =?utf-8?q?n_additions_and_code_clarity_around_PyJavaType=2Ejava=2E?= Message-ID: <20190428160343.1.B8FEAA6208E232D4@mg.python.org> https://hg.python.org/jython/rev/e233f3fc6fde changeset: 8238:e233f3fc6fde user: Jeff Allen date: Mon Apr 22 10:50:32 2019 +0100 summary: (Trivial) documentation additions and code clarity around PyJavaType.java. files: src/org/python/core/PyJavaType.java | 43 ++++++-- src/org/python/core/PyMethodDescr.java | 8 +- src/org/python/core/PyReflectedConstructor.java | 27 +++-- src/org/python/core/PyReflectedFunction.java | 8 +- src/org/python/core/ReflectedArgs.java | 48 +++++---- 5 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/org/python/core/PyJavaType.java b/src/org/python/core/PyJavaType.java --- a/src/org/python/core/PyJavaType.java +++ b/src/org/python/core/PyJavaType.java @@ -1,3 +1,5 @@ +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; import org.python.core.util.StringUtil; @@ -465,14 +467,17 @@ } } } - // Methods must be in resolution order. See issue #2391 for detail. + // Methods must be in resolution order. See issue bjo #2391 for detail. Arrays.sort(methods, new MethodComparator(new ClassComparator())); - // Add methods, also accumulating them in reflectedFuncs, and spotting Java Bean members. + /* Add methods, also accumulating them in reflectedFuncs, and spotting Java Bean members. */ ArrayList reflectedFuncs = new ArrayList<>(methods.length); Map props = Generic.map(); Map> events = Generic.map(); + + // First pass skip inherited (and certain "ignored") methods. addMethods(baseClass, reflectedFuncs, props, events, methods); + // Add inherited and previously ignored methods addInheritedMethods(reflectedFuncs, methods); // Add fields declared on this type @@ -625,10 +630,11 @@ } /** - * Process the given class for methods defined on the target class itself (the - * fromClass), rather than inherited. - * - * This is exclusively a helper method for {@link #init(Set)}. + * Add descriptors to this type's dictionary ({@code __dict__}) for methods defined on the + * target class itself (the {@code fromClass}), where not inherited. One descriptor is created + * for each simple name and a signature for every method with that simple name is added to the + * descriptor. See also {@link #addInheritedMethods(List, Method[])}. This is exclusively a + * helper method for {@link #init(Set)}. * * @param baseClass ancestor of the target class * @param reflectedFuncs to which reflected functions are added for further processing @@ -642,7 +648,6 @@ boolean isInAwt = name.startsWith("java.awt.") && name.indexOf('.', 9) == -1; - // First pass skip inherited (and certain "ignored") methods. for (Method meth : methods) { if (!declaredHere(baseClass, meth) || ignore(meth)) { continue; @@ -663,10 +668,12 @@ PyReflectedFunction reflfunc = (PyReflectedFunction) dict.__finditem__(nmethname); if (reflfunc == null) { + // A new descriptor is required reflfunc = new PyReflectedFunction(meth); reflectedFuncs.add(reflfunc); dict.__setitem__(nmethname, reflfunc); } else { + // A descriptor for the same simple name exists: add a signature to it. reflfunc.addMethod(meth); } @@ -752,15 +759,29 @@ } /** - * Process the given class for methods inherited from ancestor classes. - * - * This is exclusively a helper method for {@link #init(Set)}. + * Add descriptors to this type's dictionary ({@code __dict__}) for methods inherited from + * ancestor classes. This is exclusively a helper method for {@link #init(Set)}. + *

+ * Python finds an inherited method by looking in the dictionaries of types along the MRO. This + * is does not directly emulate the signature polymorphism of Java. Even though the entries of + * the MRO include the {@code PyType}s of the Java ancestors of this class, each type's + * dictionary is keyed on the simple name of the method. For the present class, and at the point + * where this method is called, any method defined on this class has created a descriptor entry + * for that method name (see {@link #addMethods(Class, List, Map, Map, Method[])}), but only for + * the signatures defined directly in this class. If any method of the same simple name is + * inherited in Java from a super-class, it is now shadowed by this entry as far as Python is + * concerned. The purpose of this method is to add the shadowed method signatures. + *

+ * For example {@code AbstractCollection} defines {@code add(E)}, and {@code AbstractList} + * inherits it but also defines {@code add(int, E)} (giving control of the insertion point). + * When {@link #addMethods(Class, List, Map, Map, Method[])} completes, the "add" descriptor in + * {@code type(AbstractList)}, represents only {@code add(int, E)}, and we must add the + * inherited signature for {@code add(E)}. * * @param reflectedFuncs to which reflected functions are added for further processing * @param methods of the target class */ private void addInheritedMethods(List reflectedFuncs, Method[] methods) { - // Add inherited and previously ignored methods for (Method meth : methods) { String nmethname = normalize(meth.getName()); PyReflectedFunction reflfunc = (PyReflectedFunction) dict.__finditem__(nmethname); diff --git a/src/org/python/core/PyMethodDescr.java b/src/org/python/core/PyMethodDescr.java --- a/src/org/python/core/PyMethodDescr.java +++ b/src/org/python/core/PyMethodDescr.java @@ -1,3 +1,5 @@ +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; import org.python.expose.ExposedGet; @@ -17,7 +19,7 @@ minargs = func.info.getMinargs(); maxargs = func.info.getMaxargs(); meth = func; - meth.setInfo(this); + meth.setInfo(this); // XXX Why modify func.info each time used? } @ExposedGet(name = "__doc__") @@ -25,10 +27,12 @@ return meth.getDoc(); } + @Override public int getMaxargs() { return maxargs; } + @Override public int getMinargs() { return minargs; } @@ -54,6 +58,7 @@ return meth.bind(args[0]).__call__(actualArgs, kwargs); } + @Override public PyException unexpectedCall(int nargs, boolean keywords) { return PyBuiltinCallable.DefaultInfo.unexpectedCall(nargs, keywords, name, minargs, maxargs); @@ -78,6 +83,7 @@ * * @return a name String */ + @Override @ExposedGet(name = "__name__") public String getName() { return name; diff --git a/src/org/python/core/PyReflectedConstructor.java b/src/org/python/core/PyReflectedConstructor.java --- a/src/org/python/core/PyReflectedConstructor.java +++ b/src/org/python/core/PyReflectedConstructor.java @@ -1,4 +1,6 @@ // Copyright (c) Corporation for National Research Initiatives +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; import java.lang.reflect.Constructor; @@ -40,7 +42,7 @@ // Check for a matching constructor to call if (nargs > 0) { // PyArgsKeywordsCall signature, if present, is the first if (argslist[0].matches(null, args, keywords, callData)) { - method = argslist[0].data; + method = argslist[0].method; consumes_keywords = argslist[0].flags == ReflectedArgs.PyArgsKeywordsCall; } else { allArgs = args; @@ -52,7 +54,7 @@ } for (; i < nargs; i++) { if (argslist[i].matches(null, args, Py.NoKeywords, callData)) { - method = argslist[i].data; + method = argslist[i].method; break; } } @@ -116,7 +118,7 @@ } ReflectedCallData callData = new ReflectedCallData(); Object method = null; - + // If we have keyword args, there are two ways this can be handled; // a) we find a constructor that takes keyword args, and use it. // b) we don't, in which case we strip the keyword args, and pass the @@ -129,25 +131,25 @@ boolean usingKeywordArgsCtor = false; if (nkeywords > 0) { // We have keyword args. - + // Look for a constructor; the ReflectedArgs#matches() method exits early in the case // where keyword args are used int n = nargs; for (int i = 0; i < n; i++) { rargs = argslist[i]; if (rargs.matches(null, args, keywords, callData)) { - method = rargs.data; + method = rargs.method; break; } } - + if (method != null) { // Constructor found that will accept the keyword args usingKeywordArgsCtor = true; } else { // No constructor found that will take keyword args - - // Remove the keyword args + + // Remove the keyword args args = new PyObject[allArgs.length - nkeywords]; System.arraycopy(allArgs, 0, args, 0, args.length); @@ -155,7 +157,7 @@ for (int i = 0; i < n; i++) { rargs = argslist[i]; if (rargs.matches(null, args, Py.NoKeywords, callData)) { - method = rargs.data; + method = rargs.method; break; } } @@ -166,12 +168,12 @@ for (int i = 0; i < n; i++) { rargs = argslist[i]; if (rargs.matches(null, args, Py.NoKeywords, callData)) { - method = rargs.data; + method = rargs.method; break; } } } - + // Throw an error if no valid set of arguments if (method == null) { throwError(callData.errArg, args.length, false, false); @@ -217,8 +219,9 @@ msg += " " + sup.getName(); } throw Py.TypeError(msg); - } else + } else { throw Py.JavaError(e); + } } catch (Throwable t) { throw Py.JavaError(t); } diff --git a/src/org/python/core/PyReflectedFunction.java b/src/org/python/core/PyReflectedFunction.java --- a/src/org/python/core/PyReflectedFunction.java +++ b/src/org/python/core/PyReflectedFunction.java @@ -1,4 +1,6 @@ // Copyright (c) Corporation for National Research Initiatives +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; import java.lang.reflect.Method; @@ -169,7 +171,7 @@ throwError(callData.errArg, args.length, self != null, keywords.length != 0); } Object cself = callData.self; - Method m = (Method)match.data; + Method m = (Method)match.method; // If this is a direct call to a Java class instance method with a PyProxy instance as the // arg, use the super__ version to actually route this through the method on the class. @@ -344,7 +346,9 @@ public int traverse(Visitproc visit, Object arg) { if (__module__ != null) { int res = visit.visit(__module__, arg); - if (res != 0) return res; + if (res != 0) { + return res; + } } return __doc__ != null ? visit.visit(__doc__, arg) : 0; } diff --git a/src/org/python/core/ReflectedArgs.java b/src/org/python/core/ReflectedArgs.java --- a/src/org/python/core/ReflectedArgs.java +++ b/src/org/python/core/ReflectedArgs.java @@ -1,10 +1,18 @@ // Copyright (c) Corporation for National Research Initiatives +// Copyright (c)2019 Jython Developers. +// Licensed to PSF under a Contributor Agreement. package org.python.core; +import java.lang.reflect.Member; + +/** Map the signature of a method to the {@code Method} itself, within the context of a given simple name. This is used in support of signature polymorphism in Java methods and constructors reflected into Python. **/ public class ReflectedArgs { + + /** The types of arguments defining this signature (key) */ public Class[] args; - public Object data; + /** The specific method (or constructor). */ + public Member method; public Class declaringClass; @@ -20,21 +28,21 @@ public static final int PyArgsKeywordsCall = 2; - public ReflectedArgs(Object data, Class[] args, Class declaringClass, boolean isStatic) { - this(data, args, declaringClass, isStatic, false); + public ReflectedArgs(Member method, Class[] args, Class declaringClass, boolean isStatic) { + this(method, args, declaringClass, isStatic, false); } - public ReflectedArgs(Object data, Class[] args, Class declaringClass, boolean isStatic, boolean isVarArgs) { - this.data = data; + public ReflectedArgs(Member method, Class[] args, Class declaringClass, boolean isStatic, + boolean isVarArgs) { + this.method = method; this.args = args; this.declaringClass = declaringClass; this.isStatic = isStatic; - this.isVarArgs = isVarArgs; // only used for varargs matching; it should be added after the unboxed form - + // only used for varargs matching; it should be added after the unboxed form + this.isVarArgs = isVarArgs; if (args.length == 1 && args[0] == PyObject[].class) { this.flags = PyArgsCall; - } else if (args.length == 2 && args[0] == PyObject[].class - && args[1] == String[].class) { + } else if (args.length == 2 && args[0] == PyObject[].class && args[1] == String[].class) { this.flags = PyArgsKeywordsCall; } else { this.flags = StandardCall; @@ -52,8 +60,8 @@ // if (isStatic ? self != null : self == null) return Py.NoConversion; /* Ugly code to handle mismatch in static vs. instance functions... */ /* - * Will be very inefficient in cases where static and instance functions - * both exist with same names and number of args + * Will be very inefficient in cases where static and instance functions both exist with + * same names and number of args */ if (this.isStatic) { if (self != null) { @@ -136,7 +144,7 @@ for (int i = 0; i < n; i++) { PyObject pyArg = pyArgs[i]; - Class targetClass = this.args[i]; + Class targetClass = this.args[i]; Object javaArg = pyArg.__tojava__(targetClass); javaArgs[i] = javaArg; if (javaArg == Py.NoConversion) { @@ -153,13 +161,11 @@ private PyObject[] ensureBoxedVarargs(PyObject[] pyArgs, int n) { if (pyArgs.length == 0) { // If there are no args return an empty list - return new PyObject[]{new PyList()}; + return new PyObject[] {new PyList()}; } PyObject lastArg = pyArgs[pyArgs.length - 1]; - if (lastArg instanceof PySequenceList || - lastArg instanceof PyArray || - lastArg instanceof PyXRange || - lastArg instanceof PyIterator) { + if (lastArg instanceof PySequenceList || lastArg instanceof PyArray + || lastArg instanceof PyXRange || lastArg instanceof PyIterator) { // NOTE that the check is against PySequenceList, not PySequence, // because certain Java <=> Python semantics currently require this // additional strictness. Perhaps this can be relaxed. @@ -236,9 +242,8 @@ } /* - * Returns 0 iff arg1 == arg2 Returns +/-1 iff arg1 and arg2 are - * unimportantly different Returns +/-2 iff arg1 and arg2 are significantly - * different + * Returns 0 iff arg1 == arg2 Returns +/-1 iff arg1 and arg2 are unimportantly different Returns + * +/-2 iff arg1 and arg2 are significantly different */ public static int compare(Class arg1, Class arg2) { int p1 = precedence(arg1); @@ -320,7 +325,8 @@ @Override public String toString() { - String s = declaringClass + ", static=" + isStatic + ", varargs=" + isVarArgs + ",flags=" + flags + ", " + data + "\n"; + String s = declaringClass + ", static=" + isStatic + ", varargs=" + isVarArgs + ",flags=" + + flags + ", " + method + "\n"; s = s + "\t("; for (Class arg : args) { s += arg.getName() + ", "; -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Mon Apr 29 14:20:19 2019 From: jython-checkins at python.org (jeff.allen) Date: Mon, 29 Apr 2019 18:20:19 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Close_underlying_file_in_F?= =?utf-8?q?ileIO=2Eclose=2C_even_on_error=2E?= Message-ID: <20190429182019.1.8AEF7A5F44DAA645@mg.python.org> https://hg.python.org/jython/rev/c8ee321409c2 changeset: 8240:c8ee321409c2 user: Jeff Allen date: Mon Apr 29 07:25:09 2019 +0100 summary: Close underlying file in FileIO.close, even on error. And some other bugs in tests that leave files open. files: Lib/test/test_io.py | 2 + Lib/test/test_java_integration.py | 3 +- src/org/python/modules/_io/PyFileIO.java | 19 ++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2203,6 +2203,7 @@ self.assertEqual(s, prefix.decode("ascii")) self.assertEqual(f.tell(), prefix_size) self.assertEqual(f.readline(), u_suffix) + f.close() def test_seeking_too(self): # Regression test for a specific bug @@ -2215,6 +2216,7 @@ f._CHUNK_SIZE = 2 f.readline() f.tell() + f.close() def test_seek_and_tell(self): #Test seek/tell using the StatefulIncrementalDecoder. diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -117,7 +117,7 @@ def test_awt_hack(self): # We ignore several deprecated methods in java.awt.* in favor of bean properties that were - # addded in Java 1.1. This tests that one of those bean properties is visible. + # added in Java 1.1. This tests that one of those bean properties is visible. c = Container() c.size = 400, 300 self.assertEquals(Dimension(400, 300), c.size) @@ -128,6 +128,7 @@ def tearDown(self): sys.stdout = self.orig_stdout + test_support.unlink(test_support.TESTFN) def test_stdout_outputstream(self): out = FileOutputStream(test_support.TESTFN) diff --git a/src/org/python/modules/_io/PyFileIO.java b/src/org/python/modules/_io/PyFileIO.java --- a/src/org/python/modules/_io/PyFileIO.java +++ b/src/org/python/modules/_io/PyFileIO.java @@ -360,15 +360,18 @@ @ExposedMethod final synchronized void FileIO_close() { - // Close this object to further input (also calls flush) - super.close(); - // Now close downstream (if required to) - if (closefd) { - ioDelegate.close(); + try { + // Close this object to further input (also calls flush) + super.close(); + } finally { + // Now close downstream (if required to) + if (closefd) { + ioDelegate.close(); + } + // This saves us doing two tests for each action (when the file is open) + readable = false; + writable = false; } - // This saves us doing two tests for each action (when the file is open) - readable = false; - writable = false; } @Override -- Repository URL: https://hg.python.org/jython