From 38fe45b114c6b1d9a782cef12fedd75b132ecede Mon Sep 17 00:00:00 2001 From: Jeff Allen Date: Thu, 23 Apr 2026 20:56:38 +0100 Subject: [PATCH 1/3] Formatting only of AstList source. We do this first so that the material change is evident. --- src/org/python/core/AstList.java | 117 ++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/src/org/python/core/AstList.java b/src/org/python/core/AstList.java index 882868fed..17959a799 100644 --- a/src/org/python/core/AstList.java +++ b/src/org/python/core/AstList.java @@ -1,8 +1,7 @@ /* - * I don't like that this is in org.python.core, but PySequence has package - * private methods that I want to override. Hopefully we will clean up the - * PyList hierarchy in the future, and then maybe we will find a more sensible - * place for this class. + * I don't like that this is in org.python.core, but PySequence has package private methods that I + * want to override. Hopefully we will clean up the PyList hierarchy in the future, and then maybe + * we will find a more sensible place for this class. */ package org.python.core; @@ -27,8 +26,9 @@ public class AstList extends PySequence implements Cloneable, List, Traverseproc private final static PyString[] fields = new PyString[0]; @ExposedGet(name = "_fields") - public PyString[] get_fields() { return fields; } - + public PyString[] get_fields() { + return fields; + } /** The underlying Java List. */ private List data; @@ -104,7 +104,7 @@ final void astlist___setitem__(PyObject o, PyObject def) { @ExposedMethod final PyObject astlist___getitem__(PyObject o) { PyObject ret = seq___finditem__(o); - if(ret == null) { + if (ret == null) { throw Py.IndexError("index out of range: " + o); } return ret; @@ -127,7 +127,7 @@ final PyObject astlist___getslice__(PyObject start, PyObject stop, PyObject step @ExposedMethod(defaults = "null") final void astlist___setslice__(PyObject start, PyObject stop, PyObject step, PyObject value) { - if(value == null) { + if (value == null) { value = step; step = null; } @@ -139,6 +139,7 @@ final void astlist___delslice__(PyObject start, PyObject stop, PyObject step) { seq___delslice__(start, stop, step); } + @Override public PyObject __imul__(PyObject o) { return astlist___imul__(o); } @@ -171,6 +172,7 @@ final PyObject astlist___imul__(PyObject o) { return this; } + @Override public PyObject __mul__(PyObject o) { return astlist___mul__(o); } @@ -183,6 +185,7 @@ final PyObject astlist___mul__(PyObject o) { return repeat(o.asIndex(Py.OverflowError)); } + @Override public PyObject __rmul__(PyObject o) { return astlist___rmul__(o); } @@ -195,6 +198,7 @@ final PyObject astlist___rmul__(PyObject o) { return repeat(o.asIndex(Py.OverflowError)); } + @Override public PyObject __iadd__(PyObject other) { return astlist___iadd__(other); } @@ -220,6 +224,7 @@ final PyObject astlist___iadd__(PyObject o) { return this; } + @Override public PyObject __add__(PyObject other) { return astlist___add__(other); } @@ -228,17 +233,18 @@ public PyObject __add__(PyObject other) { final PyObject astlist___add__(PyObject o) { AstList sum = null; Object oList = o.__tojava__(List.class); - if(oList != Py.NoConversion && oList != null) { + if (oList != Py.NoConversion && oList != null) { List otherList = (List) oList; sum = new AstList(); sum.extend(this); - for(Iterator i = otherList.iterator(); i.hasNext();) { + for (Iterator i = otherList.iterator(); i.hasNext();) { sum.add(i.next()); } } return sum; } + @Override public PyObject __radd__(PyObject o) { return astlist___radd__(o); } @@ -255,6 +261,7 @@ final PyObject astlist___radd__(PyObject o) { return sum; } + @Override public int __len__() { return data.size(); } @@ -278,6 +285,7 @@ final void astlist_append(PyObject o) { data.add(o); } + @Override public Object clone() { return new AstList(this); } @@ -285,8 +293,8 @@ public Object clone() { @ExposedMethod final int astlist_count(PyObject value) { int count = 0; - for(Object o : data) { - if(o.equals(value)) { + for (Object o : data) { + if (o.equals(value)) { count++; } } @@ -332,34 +340,35 @@ private int _index(PyObject o, String message, int start, int stop) { // Follow Python 2.3+ behavior int validStop = boundToSequence(stop); int validStart = boundToSequence(start); - for(int i = validStart; i < validStop && i < size(); i++) { - if(data.get(i).equals(o)) { + for (int i = validStart; i < validStop && i < size(); i++) { + if (data.get(i).equals(o)) { return i; } } throw Py.ValueError(message); } + @Override protected void del(int i) { data.remove(i); } protected void delRange(int start, int stop, int step) { - if(step >= 1) { - for(int i = start; i < stop; i += step) { + if (step >= 1) { + for (int i = start; i < stop; i += step) { remove(i); i--; stop--; } - } else if(step < 0) { - for(int i = start; i >= 0 && i >= stop; i += step) { + } else if (step < 0) { + for (int i = start; i >= 0 && i >= stop; i += step) { remove(i); } } } @ExposedMethod - final void astlist_extend(PyObject iterable){ + final void astlist_extend(PyObject iterable) { int length = size(); setslice(length, length, 1, iterable); } @@ -368,18 +377,19 @@ public void extend(PyObject iterable) { astlist_extend(iterable); } + @Override protected PyObject getslice(int start, int stop, int step) { - if(step > 0 && stop < start) { + if (step > 0 && stop < start) { stop = start; } int n = sliceLength(start, stop, step); List newList = data.subList(start, stop); - if(step == 1) { + if (step == 1) { newList = data.subList(start, stop); return new AstList(newList, adapter); } int j = 0; - for(int i = start; j < n; i += step) { + for (int i = start; j < n; i += step) { newList.set(j, data.get(i)); j++; } @@ -392,17 +402,17 @@ public void insert(int index, PyObject o) { @ExposedMethod final void astlist_insert(int index, PyObject o) { - if(index < 0) { + if (index < 0) { index = Math.max(0, size() + index); } - if(index > size()) { + if (index > size()) { index = size(); } data.add(index, o); } @ExposedMethod - final void astlist_remove(PyObject value){ + final void astlist_remove(PyObject value) { del(_index(value, "astlist.remove(x): x not in list", 0, size())); } @@ -430,13 +440,14 @@ public PyObject pop(int n) { @ExposedMethod(defaults = "-1") final PyObject astlist_pop(int n) { if (adapter == null) { - return (PyObject)data.remove(n); + return (PyObject) data.remove(n); } Object element = data.remove(n); return adapter.ast2py(element); } + @Override protected PyObject repeat(int count) { if (count < 0) { count = 0; @@ -448,22 +459,23 @@ protected PyObject repeat(int count) { } List newList = new ArrayList(); - for(int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { newList.addAll(data); } return new AstList(newList); } + @Override protected void setslice(int start, int stop, int step, PyObject value) { - if(stop < start) { + if (stop < start) { stop = start; } if (value instanceof PySequence) { PySequence sequence = (PySequence) value; setslicePySequence(start, stop, step, sequence); } else if (value instanceof List) { - List list = (List)value.__tojava__(List.class); - if(list != null && list != Py.NoConversion) { + List list = (List) value.__tojava__(List.class); + if (list != null && list != Py.NoConversion) { setsliceList(start, stop, step, list); } } else { @@ -473,10 +485,10 @@ protected void setslice(int start, int stop, int step, PyObject value) { protected void setslicePySequence(int start, int stop, int step, PySequence value) { if (step != 0) { - if(value == this) { + if (value == this) { PyList newseq = new PyList(); PyObject iter = value.__iter__(); - for(PyObject item = null; (item = iter.__iternext__()) != null;) { + for (PyObject item = null; (item = iter.__iternext__()) != null;) { newseq.append(item); } value = newseq; @@ -489,11 +501,11 @@ protected void setslicePySequence(int start, int stop, int step, PySequence valu } protected void setsliceList(int start, int stop, int step, List value) { - if(step != 1) { + if (step != 1) { throw Py.TypeError("setslice with java.util.List and step != 1 not supported yet"); } int n = value.size(); - for(int i = 0; i < n; i++) { + for (int i = 0; i < n; i++) { data.add(i + start, value.get(i)); } } @@ -511,58 +523,72 @@ protected void setsliceIterable(int start, int stop, int step, PyObject value) { setslicePySequence(start, stop, step, new PyList(seq)); } + @Override public void add(int index, Object element) { data.add(index, element); } + @Override public boolean add(Object o) { return data.add(o); } + @Override public boolean addAll(int index, Collection c) { return data.addAll(index, c); } + @Override public boolean addAll(Collection c) { return data.addAll(c); } + @Override public void clear() { data.clear(); } + @Override public boolean contains(Object o) { return data.contains(o); } + @Override public boolean containsAll(Collection c) { return data.containsAll(c); } + @Override public Object get(int index) { return data.get(index); } + @Override public int indexOf(Object o) { return data.indexOf(o); } + @Override public boolean isEmpty() { return data.isEmpty(); } + @Override public Iterator iterator() { return data.iterator(); } + @Override public int lastIndexOf(Object o) { return data.lastIndexOf(o); } + @Override public ListIterator listIterator() { return data.listIterator(); } + @Override public ListIterator listIterator(int index) { return data.listIterator(index); } @@ -576,13 +602,15 @@ public void pyadd(int index, PyObject element) { data.add(index, element); } + @Override public PyObject pyget(int index) { if (adapter == null) { - return (PyObject)data.get(index); + return (PyObject) data.get(index); } return adapter.ast2py(data.get(index)); } + @Override public void pyset(int index, PyObject element) { if (adapter == null) { data.set(index, element); @@ -592,58 +620,69 @@ public void pyset(int index, PyObject element) { } } + @Override public Object remove(int index) { return data.remove(index); } + @Override public boolean remove(Object o) { return data.remove(o); } + @Override public boolean removeAll(Collection c) { return data.removeAll(c); } + @Override public boolean retainAll(Collection c) { return data.retainAll(c); } + @Override public Object set(int index, Object element) { return data.set(index, element); } + @Override public int size() { return data.size(); } + @Override public List subList(int fromIndex, int toIndex) { return data.subList(fromIndex, toIndex); } + @Override public Object[] toArray() { return data.toArray(); } + @Override public Object[] toArray(Object[] a) { return data.toArray(a); } + @Override public Object __tojava__(Class c) { - if(c.isInstance(this)) { + if (c.isInstance(this)) { return this; } return Py.NoConversion; } - /* Traverseproc implementation */ @Override public int traverse(Visitproc visit, Object arg) { int retVal; - for (Object ob: data) { + for (Object ob : data) { if (ob instanceof PyObject) { retVal = visit.visit((PyObject) ob, arg); - if (retVal != 0) return retVal; + if (retVal != 0) { + return retVal; + } } } return 0; From 14def5e1f5fb3ee1fd4bb93ba24eb8fb2a072183 Mon Sep 17 00:00:00 2001 From: Jeff Allen Date: Thu, 23 Apr 2026 22:16:29 +0100 Subject: [PATCH 2/3] Handle slice at end of list as append Fixes #356 --- Lib/test/test_ast_jy.py | 25 ++++++++++++++++++++++++- src/org/python/core/AstList.java | 9 +++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_ast_jy.py b/Lib/test/test_ast_jy.py index 7f027bd96..f1fb4ec91 100644 --- a/Lib/test/test_ast_jy.py +++ b/Lib/test/test_ast_jy.py @@ -146,10 +146,33 @@ def test_empty_init(self): #ast.stmt() #ast.unaryop() +class TestAstList(unittest.TestCase): + """Supplementary tests for org.python.core.AstList""" + + def test_concat(self): + # Issue gh-356 Java IndexOOB error on append + body = ast.parse('x=1').body # _ast.AstList + p = [ast.Pass()] # PyList + x = body + p + self.assertEqual(2, len(x)) + self.assertIsInstance(x[1], ast.Pass) + + def test_append(self): + # Issue gh-356 Java IndexOOB error on append + body = ast.parse('x=1').body # _ast.AstList + p = [ast.Pass()] # PyList + body += p + self.assertEqual(2, len(body)) + self.assertIsInstance(body[1], ast.Pass) + + #============================================================================== def test_main(verbose=None): - test_classes = [TestCompile] + test_classes = [ + TestCompile, + TestAstList + ] test_support.run_unittest(*test_classes) if __name__ == "__main__": diff --git a/src/org/python/core/AstList.java b/src/org/python/core/AstList.java index 17959a799..683102c24 100644 --- a/src/org/python/core/AstList.java +++ b/src/org/python/core/AstList.java @@ -493,9 +493,14 @@ protected void setslicePySequence(int start, int stop, int step, PySequence valu } value = newseq; } - int n = value.__len__(); + int size = __len__(), n = value.__len__(); for (int i = 0, j = start; i < n; i++, j += step) { - pyset(j, value.pyget(i)); + PyObject item = value.pyget(i); + if (j < size) { + pyset(j, item); + } else { + pyadd(item); + } } } } From 683a47438a3027fc371562e2bb8c3b2d239ae40a Mon Sep 17 00:00:00 2001 From: Jeff Allen Date: Sat, 25 Apr 2026 11:32:39 +0100 Subject: [PATCH 3/3] Add news entry and more precise tests --- Lib/test/test_ast_jy.py | 14 ++++++-------- NEWS | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_ast_jy.py b/Lib/test/test_ast_jy.py index f1fb4ec91..2dbe7991f 100644 --- a/Lib/test/test_ast_jy.py +++ b/Lib/test/test_ast_jy.py @@ -151,19 +151,17 @@ class TestAstList(unittest.TestCase): def test_concat(self): # Issue gh-356 Java IndexOOB error on append - body = ast.parse('x=1').body # _ast.AstList - p = [ast.Pass()] # PyList + body = ast.parse('x=1; global y; y+=x*2').body # _ast.AstList + p = [ast.Pass(), ast.Break()] # PyList x = body + p - self.assertEqual(2, len(x)) - self.assertIsInstance(x[1], ast.Pass) + self.assertEqual(x[-2:], p) def test_append(self): # Issue gh-356 Java IndexOOB error on append - body = ast.parse('x=1').body # _ast.AstList - p = [ast.Pass()] # PyList + body = ast.parse('x=1; global y; y+=x*2').body # _ast.AstList + p = [ast.Pass(), ast.Break()] # PyList body += p - self.assertEqual(2, len(body)) - self.assertIsInstance(body[1], ast.Pass) + self.assertEqual(body[-2:], p) #============================================================================== diff --git a/NEWS b/NEWS index ae5ba7cd0..e97d7662a 100644 --- a/NEWS +++ b/NEWS @@ -29,8 +29,9 @@ New Features deprecated for removal in Java 26. See https://github.com/jnr/jffi/issues/165 Jython 2.7.5a1 Bugs fixed - - [ GH-382 ] Java EE Servlet Namespace Has Been Changed From javax.servlet to jakarta.servlet - - [ GH-84 ] PyServlet Will Need To Use The jakarta.servlet Namespace #84 + - [ GH-382 ] Java EE Servlet namespace changed from javax.servlet to jakarta.servlet + - [ GH-356 ] _ast.astlist.__add__ leads to Java exception + - [ GH-84 ] PyServlet will need to use the jakarta.servlet namespace ==============================================================================