1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
Origin: https://github.com/pypr/compyle/pull/124
From: Bastian Germann <bage@debian.org>
Date: Tue, 24 Feb 2026 23:40:24 +0100
Subject: Support Python 3.14 AST: handle ast.Constant and use ast_utils helpers
---
diff --git a/compyle/ast_utils.py b/compyle/ast_utils.py
index e622142..b27aad0 100644
--- a/compyle/ast_utils.py
+++ b/compyle/ast_utils.py
@@ -174,3 +174,27 @@ def has_return(code):
"""Returns True of the node has a return statement.
"""
return has_node(code, ast.Return)
+
+
+def is_str_node(node):
+ """Return True if the AST node represents a string literal.
+
+ Works on both older ASTs (ast.Str) and newer ones (ast.Constant).
+ """
+ if isinstance(node, ast.Constant):
+ return isinstance(node.value, str)
+ Str = getattr(ast, 'Str', None)
+ return Str is not None and isinstance(node, Str)
+
+
+def get_str_value(node):
+ """Return the string value for a string AST node.
+
+ Returns None if the node is not a string node.
+ """
+ if isinstance(node, ast.Constant):
+ return node.value if isinstance(node.value, str) else None
+ Str = getattr(ast, 'Str', None)
+ if Str is not None and isinstance(node, Str):
+ return node.s
+ return None
diff --git a/compyle/cython_generator.py b/compyle/cython_generator.py
index a0e7e51..d84c23c 100644
--- a/compyle/cython_generator.py
+++ b/compyle/cython_generator.py
@@ -21,7 +21,7 @@
from .types import KnownType, Undefined, get_declare_info
from .config import get_config
-from .ast_utils import get_assigned, has_return
+from .ast_utils import get_assigned, has_return, get_str_value, is_str_node
from .utils import getsourcelines
logger = logging.getLogger(__name__)
@@ -247,11 +247,12 @@ def parse_declare(code):
if call.func.id != 'declare':
raise CodeGenerationError('Unknown declare statement: %s' % code)
arg0 = call.args[0]
- if not isinstance(arg0, ast.Str):
- err = 'Type should be a string, given :%r' % arg0.s
+ typestr = get_str_value(arg0)
+ if typestr is None:
+ err = 'Type should be a string, given :%r' % getattr(arg0, 's', getattr(arg0, 'value', arg0))
raise CodeGenerationError(err)
- return get_declare_info(arg0.s)
+ return get_declare_info(typestr)
class CythonGenerator(object):
diff --git a/compyle/jit.py b/compyle/jit.py
index 080fd42..21292a8 100644
--- a/compyle/jit.py
+++ b/compyle/jit.py
@@ -15,6 +15,7 @@
from .extern import Extern
from .utils import getsourcelines
from .profile import profile
+from .ast_utils import get_str_value
from . import array
from . import parallel
@@ -198,15 +199,18 @@ def warn(self, message, node):
warnings.warn(msg)
def visit_declare(self, node):
- if not isinstance(node.args[0], ast.Str):
+ arg0 = node.args[0]
+ type_str = get_str_value(arg0)
+ if type_str is None:
self.error("Argument to declare should be a string.", node)
- type_str = node.args[0].s
return self.get_declare_type(type_str)
def visit_cast(self, node):
- if not isinstance(node.args[1], ast.Str):
+ arg1 = node.args[1]
+ typestr = get_str_value(arg1)
+ if typestr is None:
self.error("Cast type should be a string.", node)
- return node.args[1].s
+ return typestr
def visit_address(self, node):
base_type = self.visit(node.args[0])
@@ -294,6 +298,13 @@ def visit_BinOp(self, node):
def visit_Num(self, node):
return get_ctype_from_arg(node.n)
+ def visit_Constant(self, node):
+ val = node.value
+ if isinstance(val, (int, float)):
+ return get_ctype_from_arg(val)
+ # For other constants (e.g., strings/None/bool), we return None
+ return None
+
def visit_UnaryOp(self, node):
return self.visit(node.operand)
diff --git a/compyle/template.py b/compyle/template.py
index 55e6a78..d9ff609 100644
--- a/compyle/template.py
+++ b/compyle/template.py
@@ -4,6 +4,7 @@
from .types import kwtype_to_annotation
import mako.template
+from .ast_utils import get_str_value
getfullargspec = inspect.getfullargspec
@@ -45,8 +46,14 @@ def _get_code(self):
args += extra_args
arg_string = ', '.join(args)
body = m.body[0].body
- template = body[-1].value.s
- docstring = body[0].value.s if len(body) == 2 else ''
+ # Extract template and docstring in an AST-version-agnostic way
+ last_val = body[-1].value
+ template = get_str_value(last_val) or ''
+
+ docstring = ''
+ if len(body) == 2:
+ first_val = body[0].value
+ docstring = get_str_value(first_val) or ''
name = self.name
sig = 'def {name}({args}):\n """{docs}\n """'.format(
name=name, args=arg_string, docs=docstring
diff --git a/compyle/translator.py b/compyle/translator.py
index 7a10a92..6d46fa9 100644
--- a/compyle/translator.py
+++ b/compyle/translator.py
@@ -26,6 +26,7 @@
CodeGenerationError, KnownType, Undefined, all_numeric
)
from .utils import getsource
+from .ast_utils import is_str_node, get_str_value
PY_VER = sys.version_info.major
@@ -234,11 +235,11 @@ def _indent_block(self, code):
return '\n'.join(pad + x for x in lines)
def _remove_docstring(self, body):
- if body and isinstance(body[0], ast.Expr) and \
- isinstance(body[0].value, ast.Str):
- return body[1:]
- else:
- return body
+ if body and isinstance(body[0], ast.Expr):
+ val = body[0].value
+ if is_str_node(val):
+ return body[1:]
+ return body
def _get_local_info(self, obj):
return None
@@ -351,9 +352,11 @@ def visit_Assign(self, node):
left, right = node.targets[0], node.value
if isinstance(right, ast.Call) and \
isinstance(right.func, ast.Name) and right.func.id == 'declare':
- if not isinstance(right.args[0], ast.Str):
+ arg0 = right.args[0]
+ s = get_str_value(arg0)
+ if s is None:
self.error("Argument to declare should be a string.", node)
- type = right.args[0].s
+ type = s
if isinstance(left, ast.Name):
self._known.add(left.id)
return self._get_variable_declaration(type, [self.visit(left)])
@@ -395,7 +398,11 @@ def visit_Call(self, node):
elif 'atomic' in node.func.id:
return self.render_atomic(node.func.id, node.args[0])
elif node.func.id == 'cast':
- return '(%s) (%s)' % (node.args[1].s, self.visit(node.args[0]))
+ arg1 = node.args[1]
+ typestr = get_str_value(arg1)
+ if typestr is None:
+ self.error("Argument to cast should be a string.", node)
+ return '(%s) (%s)' % (typestr, self.visit(node.args[0]))
else:
return '{func}({args})'.format(
func=node.func.id,
@@ -691,6 +698,22 @@ def visit_NotEq(self, node):
def visit_Num(self, node):
return literal_to_float(node.n, self._use_double)
+ def visit_Constant(self, node):
+ val = node.value
+ # Handle booleans explicitly first
+ if isinstance(val, bool):
+ return self._replacements[val]
+ # Numbers: int/float
+ if isinstance(val, (int, float)):
+ return literal_to_float(val, self._use_double)
+ # Strings
+ if isinstance(val, str):
+ return r'"%s"' % val
+ # None and other constants
+ if val in self._replacements:
+ return self._replacements[val]
+ return repr(val)
+
def visit_Or(self, node):
return '||'
|