summaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Compiler/Symtab.py
diff options
context:
space:
mode:
authornik-bes <[email protected]>2025-05-19 07:20:13 +0300
committernik-bes <[email protected]>2025-05-19 07:36:02 +0300
commit317b7368e24941ff76499f500579fd9b10f6656e (patch)
treeabbcbaea595e7d2e9f23cf59a408b3082fe4340d /contrib/tools/cython/Cython/Compiler/Symtab.py
parent6b666a52d40308ab9b3532cd8d3008b9f37cfffb (diff)
Update Cython to 3.0.10.
commit_hash:b43c96b868cd36d636192fd2c6024d9f0d2fb6f8
Diffstat (limited to 'contrib/tools/cython/Cython/Compiler/Symtab.py')
-rw-r--r--contrib/tools/cython/Cython/Compiler/Symtab.py944
1 files changed, 680 insertions, 264 deletions
diff --git a/contrib/tools/cython/Cython/Compiler/Symtab.py b/contrib/tools/cython/Cython/Compiler/Symtab.py
index 57d2188cc75..27bd204dc6f 100644
--- a/contrib/tools/cython/Cython/Compiler/Symtab.py
+++ b/contrib/tools/cython/Cython/Compiler/Symtab.py
@@ -13,25 +13,27 @@ try:
except ImportError: # Py3
import builtins
-from .Errors import warning, error, InternalError
+from ..Utils import try_finally_contextmanager
+from .Errors import warning, error, InternalError, performance_hint
from .StringEncoding import EncodedString
from . import Options, Naming
from . import PyrexTypes
from .PyrexTypes import py_object_type, unspecified_type
from .TypeSlots import (
pyfunction_signature, pymethod_signature, richcmp_special_methods,
- get_special_method_signature, get_property_accessor_signature)
+ get_slot_table, get_property_accessor_signature)
from . import Future
from . import Code
-iso_c99_keywords = set(
-['auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
+iso_c99_keywords = {
+ 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if',
'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof',
'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void',
'volatile', 'while',
- '_Bool', '_Complex'', _Imaginary', 'inline', 'restrict'])
+ '_Bool', '_Complex'', _Imaginary', 'inline', 'restrict',
+}
def c_safe_identifier(cname):
@@ -42,6 +44,28 @@ def c_safe_identifier(cname):
cname = Naming.pyrex_prefix + cname
return cname
+def punycodify_name(cname, mangle_with=None):
+ # if passed the mangle_with should be a byte string
+ # modified from PEP489
+ try:
+ cname.encode('ascii')
+ except UnicodeEncodeError:
+ cname = cname.encode('punycode').replace(b'-', b'_').decode('ascii')
+ if mangle_with:
+ # sometimes it necessary to mangle unicode names alone where
+ # they'll be inserted directly into C, because the punycode
+ # transformation can turn them into invalid identifiers
+ cname = "%s_%s" % (mangle_with, cname)
+ elif cname.startswith(Naming.pyrex_prefix):
+ # a punycode name could also be a valid ascii variable name so
+ # change the prefix to distinguish
+ cname = cname.replace(Naming.pyrex_prefix,
+ Naming.pyunicode_identifier_prefix, 1)
+
+ return cname
+
+
+
class BufferAux(object):
writable_needed = False
@@ -87,6 +111,7 @@ class Entry(object):
# doc_cname string or None C const holding the docstring
# getter_cname string C func for getting property
# setter_cname string C func for setting or deleting property
+ # is_cproperty boolean Is an inline property of an external type
# is_self_arg boolean Is the "self" arg of an exttype method
# is_arg boolean Is the arg of a method
# is_local boolean Is a local variable
@@ -134,6 +159,13 @@ class Entry(object):
# cf_used boolean Entry is used
# is_fused_specialized boolean Whether this entry of a cdef or def function
# is a specialization
+ # is_cgetter boolean Is a c-level getter function
+ # is_cpp_optional boolean Entry should be declared as std::optional (cpp_locals directive)
+ # known_standard_library_import Either None (default), an empty string (definitely can't be determined)
+ # or a string of "modulename.something.attribute"
+ # Used for identifying imports from typing/dataclasses etc
+ # pytyping_modifiers Python type modifiers like "typing.ClassVar" but also "dataclasses.InitVar"
+ # enum_int_value None or int If known, the int that corresponds to this enum value
# TODO: utility_code and utility_code_definition serves the same purpose...
@@ -141,6 +173,7 @@ class Entry(object):
borrowed = 0
init = ""
annotation = None
+ pep563_annotation = None
visibility = 'private'
is_builtin = 0
is_cglobal = 0
@@ -160,6 +193,7 @@ class Entry(object):
is_cpp_class = 0
is_const = 0
is_property = 0
+ is_cproperty = 0
doc_cname = None
getter_cname = None
setter_cname = None
@@ -203,6 +237,12 @@ class Entry(object):
error_on_uninitialized = False
cf_used = True
outer_entry = None
+ is_cgetter = False
+ is_cpp_optional = False
+ known_standard_library_import = None
+ pytyping_modifiers = None
+ enum_int_value = None
+ vtable_type = None
def __init__(self, name, cname, type, pos = None, init = None):
self.name = name
@@ -238,6 +278,19 @@ class Entry(object):
else:
return NotImplemented
+ @property
+ def cf_is_reassigned(self):
+ return len(self.cf_assignments) > 1
+
+ def make_cpp_optional(self):
+ assert self.type.is_cpp_class
+ self.is_cpp_optional = True
+ assert not self.utility_code # we're not overwriting anything?
+ self.utility_code_definition = Code.UtilityCode.load_cached("OptionalLocals", "CppSupport.cpp")
+
+ def declared_with_pytyping_modifier(self, modifier_name):
+ return modifier_name in self.pytyping_modifiers if self.pytyping_modifiers else False
+
class InnerEntry(Entry):
"""
@@ -262,6 +315,7 @@ class InnerEntry(Entry):
self.cf_assignments = outermost_entry.cf_assignments
self.cf_references = outermost_entry.cf_references
self.overloaded_alternatives = outermost_entry.overloaded_alternatives
+ self.is_cpp_optional = outermost_entry.is_cpp_optional
self.inner_entries.append(self)
def __getattr__(self, name):
@@ -291,10 +345,13 @@ class Scope(object):
# is_builtin_scope boolean Is the builtin scope of Python/Cython
# is_py_class_scope boolean Is a Python class scope
# is_c_class_scope boolean Is an extension type scope
+ # is_local_scope boolean Is a local (i.e. function/method/generator) scope
# is_closure_scope boolean Is a closure scope
+ # is_generator_expression_scope boolean A subset of closure scope used for generator expressions
# is_passthrough boolean Outer scope is passed directly
# is_cpp_class_scope boolean Is a C++ class scope
# is_property_scope boolean Is a extension type property scope
+ # is_c_dataclass_scope boolean or "frozen" is a cython.dataclasses.dataclass
# scope_prefix string Disambiguator for C names
# in_cinclude boolean Suppress C declaration code
# qualified_name string "modname" or "modname.classname"
@@ -303,22 +360,30 @@ class Scope(object):
# directives dict Helper variable for the recursive
# analysis, contains directive values.
# is_internal boolean Is only used internally (simpler setup)
+ # scope_predefined_names list of str Class variable containing special names defined by
+ # this type of scope (e.g. __builtins__, __qualname__)
is_builtin_scope = 0
is_py_class_scope = 0
is_c_class_scope = 0
is_closure_scope = 0
- is_genexpr_scope = 0
+ is_local_scope = False
+ is_generator_expression_scope = 0
+ is_comprehension_scope = 0
is_passthrough = 0
is_cpp_class_scope = 0
is_property_scope = 0
is_module_scope = 0
+ is_c_dataclass_scope = False
is_internal = 0
scope_prefix = ""
in_cinclude = 0
nogil = 0
fused_to_specific = None
return_type = None
+ scope_predefined_names = []
+ # Do ambiguous type names like 'int' and 'float' refer to the C types? (Otherwise, Python types.)
+ in_c_type_context = True
def __init__(self, name, outer_scope, parent_scope):
# The outer_scope is the next scope in the lookup chain.
@@ -347,22 +412,23 @@ class Scope(object):
self.defined_c_classes = []
self.imported_c_classes = {}
self.cname_to_entry = {}
- self.string_to_entry = {}
self.identifier_to_entry = {}
self.num_to_entry = {}
self.obj_to_entry = {}
self.buffer_entries = []
self.lambda_defs = []
self.id_counters = {}
+ for var_name in self.scope_predefined_names:
+ self.declare_var(EncodedString(var_name), py_object_type, pos=None)
def __deepcopy__(self, memo):
return self
- def merge_in(self, other, merge_unused=True, whitelist=None):
+ def merge_in(self, other, merge_unused=True, allowlist=None):
# Use with care...
entries = []
for name, entry in other.entries.items():
- if not whitelist or name in whitelist:
+ if not allowlist or name in allowlist:
if entry.used or merge_unused:
entries.append((name, entry))
@@ -390,7 +456,7 @@ class Scope(object):
def mangle(self, prefix, name = None):
if name:
- return "%s%s%s" % (prefix, self.scope_prefix, name)
+ return punycodify_name("%s%s%s" % (prefix, self.scope_prefix, name))
else:
return self.parent_scope.mangle(prefix, self.name)
@@ -436,59 +502,104 @@ class Scope(object):
for scope in sorted(self.subscopes, key=operator.attrgetter('scope_prefix')):
yield scope
+ @try_finally_contextmanager
+ def new_c_type_context(self, in_c_type_context=None):
+ old_c_type_context = self.in_c_type_context
+ if in_c_type_context is not None:
+ self.in_c_type_context = in_c_type_context
+ yield
+ self.in_c_type_context = old_c_type_context
+
+ def handle_already_declared_name(self, name, cname, type, pos, visibility):
+ """
+ Returns an entry or None
+
+ If it returns an entry, it makes sense for "declare" to keep using that
+ entry and not to declare its own.
+
+ May be overridden (e.g. for builtin scope,
+ which always allows redeclarations)
+ """
+ entry = None
+ entries = self.entries
+ old_entry = entries[name]
+
+ # Reject redeclared C++ functions only if they have a compatible type signature.
+ cpp_override_allowed = False
+ if type.is_cfunction and old_entry.type.is_cfunction and self.is_cpp():
+ # If we redefine a C++ class method which is either inherited
+ # or automatically generated (base constructor), then it's fine.
+ # Otherwise, we shout.
+ for alt_entry in old_entry.all_alternatives():
+ if type.compatible_signature_with(alt_entry.type):
+ if name == '<init>' and not type.args:
+ # Cython pre-declares the no-args constructor - allow later user definitions.
+ cpp_override_allowed = True
+ elif alt_entry.is_inherited:
+ # Note that we can override an inherited method with a compatible but not exactly equal signature, as in C++.
+ cpp_override_allowed = True
+ if cpp_override_allowed:
+ # A compatible signature doesn't mean the exact same signature,
+ # so we're taking the new signature for the entry.
+ alt_entry.type = type
+ alt_entry.is_inherited = False
+ # Updating the entry attributes which can be modified in the method redefinition.
+ alt_entry.cname = cname
+ alt_entry.pos = pos
+ entry = alt_entry
+ break
+ else:
+ cpp_override_allowed = True
+
+ if cpp_override_allowed:
+ # C++ function/method overrides with different signatures are ok.
+ pass
+ elif entries[name].is_inherited:
+ # Likewise ignore inherited classes.
+ pass
+ else:
+ if visibility == 'extern':
+ # Silenced outside of "cdef extern" blocks, until we have a safe way to
+ # prevent pxd-defined cpdef functions from ending up here.
+ warning(pos, "'%s' redeclared " % name, 1 if self.in_cinclude else 0)
+ elif visibility != 'ignore':
+ error(pos, "'%s' redeclared " % name)
+ self.entries[name].already_declared_here()
+ return None
+
+ return entry
+
+
def declare(self, name, cname, type, pos, visibility, shadow = 0, is_type = 0, create_wrapper = 0):
# Create new entry, and add to dictionary if
# name is not None. Reports a warning if already
# declared.
- if type.is_buffer and not isinstance(self, LocalScope): # and not is_type:
+ if type.is_buffer and not isinstance(self, LocalScope): # and not is_type:
error(pos, 'Buffer types only allowed as function local variables')
if not self.in_cinclude and cname and re.match("^_[_A-Z]+$", cname):
- # See http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html#Reserved-Names
+ # See https://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html#Reserved-Names
warning(pos, "'%s' is a reserved name in C." % cname, -1)
+
entries = self.entries
+ entry = None
if name and name in entries and not shadow:
- old_entry = entries[name]
-
- # Reject redeclared C++ functions only if they have the same type signature.
- cpp_override_allowed = False
- if type.is_cfunction and old_entry.type.is_cfunction and self.is_cpp():
- for alt_entry in old_entry.all_alternatives():
- if type == alt_entry.type:
- if name == '<init>' and not type.args:
- # Cython pre-declares the no-args constructor - allow later user definitions.
- cpp_override_allowed = True
- break
- else:
- cpp_override_allowed = True
-
- if cpp_override_allowed:
- # C++ function/method overrides with different signatures are ok.
- pass
- elif self.is_cpp_class_scope and entries[name].is_inherited:
- # Likewise ignore inherited classes.
- pass
- elif visibility == 'extern':
- # Silenced outside of "cdef extern" blocks, until we have a safe way to
- # prevent pxd-defined cpdef functions from ending up here.
- warning(pos, "'%s' redeclared " % name, 1 if self.in_cinclude else 0)
- elif visibility != 'ignore':
- error(pos, "'%s' redeclared " % name)
- entries[name].already_declared_here()
- entry = Entry(name, cname, type, pos = pos)
- entry.in_cinclude = self.in_cinclude
- entry.create_wrapper = create_wrapper
- if name:
- entry.qualified_name = self.qualify_name(name)
-# if name in entries and self.is_cpp():
-# entries[name].overloaded_alternatives.append(entry)
-# else:
-# entries[name] = entry
- if not shadow:
- entries[name] = entry
+ entry = self.handle_already_declared_name(name, cname, type, pos, visibility)
+
+ if not entry:
+ entry = Entry(name, cname, type, pos = pos)
+ entry.in_cinclude = self.in_cinclude
+ entry.create_wrapper = create_wrapper
+ if name:
+ entry.qualified_name = self.qualify_name(name)
+ if not shadow:
+ if name in entries and self.is_cpp() and type.is_cfunction and not entries[name].is_cmethod:
+ # Which means: function or cppclass method is already present
+ entries[name].overloaded_alternatives.append(entry)
+ else:
+ entries[name] = entry
if type.is_memoryviewslice:
- from . import MemoryView
- entry.init = MemoryView.memslice_entry_init
+ entry.init = type.default_value
entry.scope = self
entry.visibility = visibility
@@ -522,7 +633,8 @@ class Scope(object):
if defining:
self.type_entries.append(entry)
- if not template:
+ # don't replace an entry that's already set
+ if not template and getattr(type, "entry", None) is None:
type.entry = entry
# here we would set as_variable to an object representing this type
@@ -563,8 +675,10 @@ class Scope(object):
cname = self.mangle(Naming.type_prefix, name)
entry = self.lookup_here(name)
if not entry:
+ in_cpp = self.is_cpp()
type = PyrexTypes.CStructOrUnionType(
- name, kind, scope, typedef_flag, cname, packed)
+ name, kind, scope, typedef_flag, cname, packed,
+ in_cpp = in_cpp)
entry = self.declare_type(name, type, pos, cname,
visibility = visibility, api = api,
defining = scope is not None)
@@ -650,12 +764,12 @@ class Scope(object):
error(pos, "'%s' previously declared as '%s'" % (
entry.name, entry.visibility))
- def declare_enum(self, name, pos, cname, typedef_flag,
- visibility = 'private', api = 0, create_wrapper = 0):
+ def declare_enum(self, name, pos, cname, scoped, typedef_flag,
+ visibility='private', api=0, create_wrapper=0, doc=None):
if name:
if not cname:
if (self.in_cinclude or visibility == 'public'
- or visibility == 'extern' or api):
+ or visibility == 'extern' or api):
cname = name
else:
cname = self.mangle(Naming.type_prefix, name)
@@ -663,13 +777,21 @@ class Scope(object):
namespace = self.outer_scope.lookup(self.name).type
else:
namespace = None
- type = PyrexTypes.CEnumType(name, cname, typedef_flag, namespace)
+
+ if scoped:
+ type = PyrexTypes.CppScopedEnumType(name, cname, namespace, doc=doc)
+ else:
+ type = PyrexTypes.CEnumType(name, cname, typedef_flag, namespace, doc=doc)
else:
type = PyrexTypes.c_anon_enum_type
entry = self.declare_type(name, type, pos, cname = cname,
visibility = visibility, api = api)
+ if scoped:
+ entry.utility_code = Code.UtilityCode.load_cached("EnumClassDecl", "CppSupport.cpp")
+ self.use_entry_utility_code(entry)
entry.create_wrapper = create_wrapper
entry.enum_values = []
+
self.sue_entries.append(entry)
return entry
@@ -677,27 +799,45 @@ class Scope(object):
return self.outer_scope.declare_tuple_type(pos, components)
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None):
# Add an entry for a variable.
if not cname:
if visibility != 'private' or api:
cname = name
else:
cname = self.mangle(Naming.var_prefix, name)
- if type.is_cpp_class and visibility != 'extern':
- type.check_nullary_constructor(pos)
entry = self.declare(name, cname, type, pos, visibility)
entry.is_variable = 1
+ if type.is_cpp_class and visibility != 'extern':
+ if self.directives['cpp_locals']:
+ entry.make_cpp_optional()
+ else:
+ type.check_nullary_constructor(pos)
if in_pxd and visibility != 'extern':
entry.defined_in_pxd = 1
entry.used = 1
if api:
entry.api = 1
entry.used = 1
+ if pytyping_modifiers:
+ entry.pytyping_modifiers = pytyping_modifiers
return entry
+ def _reject_pytyping_modifiers(self, pos, modifiers, allowed=()):
+ if not modifiers:
+ return
+ for modifier in modifiers:
+ if modifier not in allowed:
+ error(pos, "Modifier '%s' is not allowed here." % modifier)
+
+ def declare_assignment_expression_target(self, name, type, pos):
+ # In most cases declares the variable as normal.
+ # For generator expressions and comprehensions the variable is declared in their parent
+ return self.declare_var(name, type, pos)
+
def declare_builtin(self, name, pos):
+ name = self.mangle_class_private_name(name)
return self.outer_scope.declare_builtin(name, pos)
def _declare_pyfunction(self, name, pos, visibility='extern', entry=None):
@@ -719,7 +859,7 @@ class Scope(object):
entry.type = py_object_type
elif entry.type is not py_object_type:
return self._declare_pyfunction(name, pos, visibility=visibility, entry=entry)
- else: # declare entry stub
+ else: # declare entry stub
self.declare_var(name, py_object_type, pos, visibility=visibility)
entry = self.declare_var(None, py_object_type, pos,
cname=name, visibility='private')
@@ -736,7 +876,7 @@ class Scope(object):
qualified_name = self.qualify_name(lambda_name)
entry = self.declare(None, func_cname, py_object_type, pos, 'private')
- entry.name = lambda_name
+ entry.name = EncodedString(lambda_name)
entry.qualified_name = qualified_name
entry.pymethdef_cname = pymethdef_cname
entry.func_cname = func_cname
@@ -759,6 +899,10 @@ class Scope(object):
cname = name
else:
cname = self.mangle(Naming.func_prefix, name)
+ inline_in_pxd = 'inline' in modifiers and in_pxd and defining
+ if inline_in_pxd:
+ # in_pxd does special things that we don't want to apply to inline functions
+ in_pxd = False
entry = self.lookup_here(name)
if entry:
if not in_pxd and visibility != entry.visibility and visibility == 'extern':
@@ -769,7 +913,8 @@ class Scope(object):
entry.cname = cname
entry.func_cname = cname
if visibility != 'private' and visibility != entry.visibility:
- warning(pos, "Function '%s' previously declared as '%s', now as '%s'" % (name, entry.visibility, visibility), 1)
+ warning(pos, "Function '%s' previously declared as '%s', now as '%s'" % (
+ name, entry.visibility, visibility), 1)
if overridable != entry.is_overridable:
warning(pos, "Function '%s' previously declared as '%s'" % (
name, 'cpdef' if overridable else 'cdef'), 1)
@@ -778,15 +923,15 @@ class Scope(object):
entry.type = entry.type.with_with_gil(type.with_gil)
else:
if visibility == 'extern' and entry.visibility == 'extern':
- can_override = False
+ can_override = self.is_builtin_scope
if self.is_cpp():
can_override = True
- elif cname:
+ elif cname and not can_override:
# if all alternatives have different cnames,
# it's safe to allow signature overrides
for alt_entry in entry.all_alternatives():
if not alt_entry.cname or cname == alt_entry.cname:
- break # cname not unique!
+ break # cname not unique!
else:
can_override = True
if can_override:
@@ -799,6 +944,17 @@ class Scope(object):
elif not in_pxd and entry.defined_in_pxd and type.compatible_signature_with(entry.type):
# TODO: check that this was done by a signature optimisation and not a user error.
#warning(pos, "Function signature does not match previous declaration", 1)
+
+ # Cython can't assume anything about cimported functions declared without
+ # an exception value. This is a performance problem mainly for nogil functions.
+ if entry.type.nogil and entry.type.exception_value is None and type.exception_value:
+ performance_hint(
+ entry.pos,
+ "No exception value declared for '%s' in pxd file.\n"
+ "Users cimporting this function and calling it without the gil "
+ "will always require an exception check.\n"
+ "Suggest adding an explicit exception value." % entry.name,
+ self)
entry.type = type
else:
error(pos, "Function signature does not match previous declaration")
@@ -806,6 +962,8 @@ class Scope(object):
entry = self.add_cfunction(name, type, pos, cname, visibility, modifiers)
entry.func_cname = cname
entry.is_overridable = overridable
+ if inline_in_pxd:
+ entry.inline_func_in_pxd = True
if in_pxd and visibility != 'extern':
entry.defined_in_pxd = 1
if api:
@@ -828,6 +986,29 @@ class Scope(object):
var_entry.scope = entry.scope
entry.as_variable = var_entry
type.entry = entry
+ if (type.exception_check and type.exception_value is None and type.nogil and
+ not pos[0].in_utility_code and
+ # don't warn about external functions here - the user likely can't do anything
+ defining and not in_pxd and not inline_in_pxd):
+ PyrexTypes.write_noexcept_performance_hint(
+ pos, self, function_name=name, void_return=type.return_type.is_void)
+ return entry
+
+ def declare_cgetter(self, name, return_type, pos=None, cname=None,
+ visibility="private", modifiers=(), defining=False, **cfunc_type_config):
+ assert all(
+ k in ('exception_value', 'exception_check', 'nogil', 'with_gil', 'is_const_method', 'is_static_method')
+ for k in cfunc_type_config
+ )
+ cfunc_type = PyrexTypes.CFuncType(
+ return_type,
+ [PyrexTypes.CFuncTypeArg("self", self.parent_type, None)],
+ **cfunc_type_config)
+ entry = self.declare_cfunction(
+ name, cfunc_type, pos, cname=None, visibility=visibility, modifiers=modifiers, defining=defining)
+ entry.is_cgetter = True
+ if cname is not None:
+ entry.func_cname = cname
return entry
def add_cfunction(self, name, type, pos, cname, visibility, modifiers, inherited=False):
@@ -875,37 +1056,81 @@ class Scope(object):
def lookup(self, name):
# Look up name in this scope or an enclosing one.
# Return None if not found.
- return (self.lookup_here(name)
- or (self.outer_scope and self.outer_scope.lookup(name))
- or None)
+
+ mangled_name = self.mangle_class_private_name(name)
+ entry = (self.lookup_here(name) # lookup here also does mangling
+ or (self.outer_scope and self.outer_scope.lookup(mangled_name))
+ or None)
+ if entry:
+ return entry
+
+ # look up the original name in the outer scope
+ # Not strictly Python behaviour but see https://github.com/cython/cython/issues/3544
+ entry = (self.outer_scope and self.outer_scope.lookup(name)) or None
+ if entry and entry.is_pyglobal:
+ self._emit_class_private_warning(entry.pos, name)
+ return entry
def lookup_here(self, name):
# Look up in this scope only, return None if not found.
+
+ entry = self.entries.get(self.mangle_class_private_name(name), None)
+ if entry:
+ return entry
+ # Also check the unmangled name in the current scope
+ # (even if mangling should give us something else).
+ # This is to support things like global __foo which makes a declaration for __foo
+ return self.entries.get(name, None)
+
+ def lookup_here_unmangled(self, name):
return self.entries.get(name, None)
+ def lookup_assignment_expression_target(self, name):
+ # For most cases behaves like "lookup_here".
+ # However, it does look outwards for comprehension and generator expression scopes
+ return self.lookup_here(name)
+
def lookup_target(self, name):
# Look up name in this scope only. Declare as Python
# variable if not found.
entry = self.lookup_here(name)
if not entry:
+ entry = self.lookup_here_unmangled(name)
+ if entry and entry.is_pyglobal:
+ self._emit_class_private_warning(entry.pos, name)
+ if not entry:
entry = self.declare_var(name, py_object_type, None)
return entry
- def lookup_type(self, name):
- entry = self.lookup(name)
+ def _type_or_specialized_type_from_entry(self, entry):
if entry and entry.is_type:
if entry.type.is_fused and self.fused_to_specific:
return entry.type.specialize(self.fused_to_specific)
return entry.type
+ def lookup_type(self, name):
+ entry = self.lookup(name)
+ # The logic here is:
+ # 1. if entry is a type then return it (and maybe specialize it)
+ # 2. if the entry comes from a known standard library import then follow that
+ # 3. repeat step 1 with the (possibly) updated entry
+
+ tp = self._type_or_specialized_type_from_entry(entry)
+ if tp:
+ return tp
+ # allow us to find types from the "typing" module and similar
+ if entry and entry.known_standard_library_import:
+ from .Builtin import get_known_standard_library_entry
+ entry = get_known_standard_library_entry(entry.known_standard_library_import)
+ return self._type_or_specialized_type_from_entry(entry)
+
def lookup_operator(self, operator, operands):
if operands[0].type.is_cpp_class:
obj_type = operands[0].type
method = obj_type.scope.lookup("operator%s" % operator)
if method is not None:
arg_types = [arg.type for arg in operands[1:]]
- res = PyrexTypes.best_match([arg.type for arg in operands[1:]],
- method.all_alternatives())
+ res = PyrexTypes.best_match(arg_types, method.all_alternatives())
if res is not None:
return res
function = self.lookup("operator%s" % operator)
@@ -915,7 +1140,7 @@ class Scope(object):
# look-up nonmember methods listed within a class
method_alternatives = []
- if len(operands)==2: # binary operators only
+ if len(operands) == 2: # binary operators only
for n in range(2):
if operands[n].type.is_cpp_class:
obj_type = operands[n].type
@@ -939,6 +1164,11 @@ class Scope(object):
operands = [FakeOperand(pos, type=type) for type in types]
return self.lookup_operator(operator, operands)
+ def _emit_class_private_warning(self, pos, name):
+ warning(pos, "Global name %s matched from within class scope "
+ "in contradiction to to Python 'class private name' rules. "
+ "This may change in a future release." % name, 1)
+
def use_utility_code(self, new_code):
self.global_scope().use_utility_code(new_code)
@@ -1000,9 +1230,9 @@ class BuiltinScope(Scope):
Scope.__init__(self, "__builtin__", PreImportScope(), None)
self.type_names = {}
- for name, definition in sorted(self.builtin_entries.items()):
- cname, type = definition
- self.declare_var(name, type, None, cname)
+ # Most entries are initialized in init_builtins, except for "bool"
+ # which is apparently a special case because it conflicts with C++ bool
+ self.declare_var("bool", py_object_type, None, "((PyObject*)&PyBool_Type)")
def lookup(self, name, language_level=None, str_is_str=None):
# 'language_level' and 'str_is_str' are passed by ModuleScope
@@ -1027,8 +1257,7 @@ class BuiltinScope(Scope):
# If python_equiv == "*", the Python equivalent has the same name
# as the entry, otherwise it has the name specified by python_equiv.
name = EncodedString(name)
- entry = self.declare_cfunction(name, type, None, cname, visibility='extern',
- utility_code=utility_code)
+ entry = self.declare_cfunction(name, type, None, cname, visibility='extern', utility_code=utility_code)
if python_equiv:
if python_equiv == "*":
python_equiv = name
@@ -1043,10 +1272,11 @@ class BuiltinScope(Scope):
entry.as_variable = var_entry
return entry
- def declare_builtin_type(self, name, cname, utility_code = None, objstruct_cname = None):
+ def declare_builtin_type(self, name, cname, utility_code=None,
+ objstruct_cname=None, type_class=PyrexTypes.BuiltinObjectType):
name = EncodedString(name)
- type = PyrexTypes.BuiltinObjectType(name, cname, objstruct_cname)
- scope = CClassScope(name, outer_scope=None, visibility='extern')
+ type = type_class(name, cname, objstruct_cname)
+ scope = CClassScope(name, outer_scope=None, visibility='extern', parent_type=type)
scope.directives = {}
if name == 'bool':
type.is_final_type = True
@@ -1055,10 +1285,12 @@ class BuiltinScope(Scope):
entry = self.declare_type(name, type, None, visibility='extern')
entry.utility_code = utility_code
- var_entry = Entry(name = entry.name,
- type = self.lookup('type').type, # make sure "type" is the first type declared...
- pos = entry.pos,
- cname = entry.type.typeptr_cname)
+ var_entry = Entry(
+ name=entry.name,
+ type=self.lookup('type').type, # make sure "type" is the first type declared...
+ pos=entry.pos,
+ cname=entry.type.typeptr_cname,
+ )
var_entry.qualified_name = self.qualify_name(name)
var_entry.is_variable = 1
var_entry.is_cglobal = 1
@@ -1075,36 +1307,12 @@ class BuiltinScope(Scope):
def builtin_scope(self):
return self
- builtin_entries = {
-
- "type": ["((PyObject*)&PyType_Type)", py_object_type],
-
- "bool": ["((PyObject*)&PyBool_Type)", py_object_type],
- "int": ["((PyObject*)&PyInt_Type)", py_object_type],
- "long": ["((PyObject*)&PyLong_Type)", py_object_type],
- "float": ["((PyObject*)&PyFloat_Type)", py_object_type],
- "complex":["((PyObject*)&PyComplex_Type)", py_object_type],
-
- "bytes": ["((PyObject*)&PyBytes_Type)", py_object_type],
- "bytearray": ["((PyObject*)&PyByteArray_Type)", py_object_type],
- "str": ["((PyObject*)&PyString_Type)", py_object_type],
- "unicode":["((PyObject*)&PyUnicode_Type)", py_object_type],
-
- "tuple": ["((PyObject*)&PyTuple_Type)", py_object_type],
- "list": ["((PyObject*)&PyList_Type)", py_object_type],
- "dict": ["((PyObject*)&PyDict_Type)", py_object_type],
- "set": ["((PyObject*)&PySet_Type)", py_object_type],
- "frozenset": ["((PyObject*)&PyFrozenSet_Type)", py_object_type],
-
- "slice": ["((PyObject*)&PySlice_Type)", py_object_type],
-# "file": ["((PyObject*)&PyFile_Type)", py_object_type], # not in Py3
+ def handle_already_declared_name(self, name, cname, type, pos, visibility):
+ # Overriding is OK in the builtin scope
+ return None
- "None": ["Py_None", py_object_type],
- "False": ["Py_False", py_object_type],
- "True": ["Py_True", py_object_type],
- }
-const_counter = 1 # As a temporary solution for compiling code in pxds
+const_counter = 1 # As a temporary solution for compiling code in pxds
class ModuleScope(Scope):
# module_name string Python name of the module
@@ -1116,7 +1324,6 @@ class ModuleScope(Scope):
# utility_code_list [UtilityCode] Queuing utility codes for forwarding to Code.py
# c_includes {key: IncludeCode} C headers or verbatim code to be generated
# See process_include() for more documentation
- # string_to_entry {string : Entry} Map string const to entry
# identifier_to_entry {string : Entry} Map identifier string const to entry
# context Context
# parent_module Scope Parent in the import namespace
@@ -1135,20 +1342,18 @@ class ModuleScope(Scope):
has_import_star = 0
is_cython_builtin = 0
old_style_globals = 0
+ scope_predefined_names = [
+ '__builtins__', '__name__', '__file__', '__doc__', '__path__',
+ '__spec__', '__loader__', '__package__', '__cached__',
+ ]
- def __init__(self, name, parent_module, context):
+ def __init__(self, name, parent_module, context, is_package=False):
from . import Builtin
self.parent_module = parent_module
outer_scope = Builtin.builtin_scope
Scope.__init__(self, name, outer_scope, parent_module)
- if name == "__init__":
- # Treat Spam/__init__.pyx specially, so that when Python loads
- # Spam/__init__.so, initSpam() is defined.
- self.module_name = parent_module.module_name
- self.is_package = True
- else:
- self.module_name = name
- self.is_package = False
+ self.is_package = is_package
+ self.module_name = name
self.module_name = EncodedString(self.module_name)
self.context = context
self.module_cname = Naming.module_cname
@@ -1169,9 +1374,6 @@ class ModuleScope(Scope):
self.undeclared_cached_builtins = []
self.namespace_cname = self.module_cname
self._cached_tuple_types = {}
- for var_name in ['__builtins__', '__name__', '__file__', '__doc__', '__path__',
- '__spec__', '__loader__', '__package__', '__cached__']:
- self.declare_var(EncodedString(var_name), py_object_type, None)
self.process_include(Code.IncludeCode("Python.h", initial=True))
def qualifying_scope(self):
@@ -1239,7 +1441,7 @@ class ModuleScope(Scope):
entry = self.declare(None, None, py_object_type, pos, 'private')
if Options.cache_builtins and name not in Code.uncachable_builtins:
entry.is_builtin = 1
- entry.is_const = 1 # cached
+ entry.is_const = 1 # cached
entry.name = name
entry.cname = Naming.builtin_prefix + name
self.cached_builtins.append(entry)
@@ -1255,25 +1457,33 @@ class ModuleScope(Scope):
# relative imports relative to this module's parent.
# Finds and parses the module's .pxd file if the module
# has not been referenced before.
- relative_to = None
+ is_relative_import = relative_level is not None and relative_level > 0
+ from_module = None
absolute_fallback = False
if relative_level is not None and relative_level > 0:
# explicit relative cimport
# error of going beyond top-level is handled in cimport node
- relative_to = self
- while relative_level > 0 and relative_to:
- relative_to = relative_to.parent_module
+ from_module = self
+
+ top_level = 1 if self.is_package else 0
+ # * top_level == 1 when file is __init__.pyx, current package (from_module) is the current module
+ # i.e. dot in `from . import ...` points to the current package
+ # * top_level == 0 when file is regular module, current package (from_module) is parent module
+ # i.e. dot in `from . import ...` points to the package where module is placed
+ while relative_level > top_level and from_module:
+ from_module = from_module.parent_module
relative_level -= 1
+
elif relative_level != 0:
# -1 or None: try relative cimport first, then absolute
- relative_to = self.parent_module
+ from_module = self.parent_module
absolute_fallback = True
module_scope = self.global_scope()
return module_scope.context.find_module(
- module_name, relative_to=relative_to, pos=pos, absolute_fallback=absolute_fallback, need_pxd=need_pxd)
+ module_name, from_module=from_module, pos=pos, absolute_fallback=absolute_fallback, relative_import=is_relative_import, need_pxd=need_pxd)
- def find_submodule(self, name):
+ def find_submodule(self, name, as_package=False):
# Find and return scope for a submodule of this module,
# creating a new empty one if necessary. Doesn't parse .pxd.
if '.' in name:
@@ -1282,10 +1492,10 @@ class ModuleScope(Scope):
submodule = None
scope = self.lookup_submodule(name)
if not scope:
- scope = ModuleScope(name, parent_module=self, context=self.context)
+ scope = ModuleScope(name, parent_module=self, context=self.context, is_package=True if submodule else as_package)
self.module_entries[name] = scope
if submodule:
- scope = scope.find_submodule(submodule)
+ scope = scope.find_submodule(submodule, as_package=as_package)
return scope
def lookup_submodule(self, name):
@@ -1364,7 +1574,7 @@ class ModuleScope(Scope):
entry = self.lookup_here(name)
if entry:
if entry.is_pyglobal and entry.as_module is scope:
- return entry # Already declared as the same module
+ return entry # Already declared as the same module
if not (entry.is_pyglobal and not entry.as_module):
# SAGE -- I put this here so Pyrex
# cimport's work across directories.
@@ -1382,14 +1592,15 @@ class ModuleScope(Scope):
return entry
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None):
# Add an entry for a global variable. If it is a Python
# object type, and not declared with cdef, it will live
# in the module dictionary, otherwise it will be a C
# global variable.
- if not visibility in ('private', 'public', 'extern'):
+ if visibility not in ('private', 'public', 'extern'):
error(pos, "Module-level variable cannot be declared %s" % visibility)
+ self._reject_pytyping_modifiers(pos, pytyping_modifiers, ('typing.Optional',)) # let's allow at least this one
if not is_cdef:
if type is unspecified_type:
type = py_object_type
@@ -1425,7 +1636,7 @@ class ModuleScope(Scope):
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility,
- api=api, in_pxd=in_pxd, is_cdef=is_cdef)
+ api=api, in_pxd=in_pxd, is_cdef=is_cdef, pytyping_modifiers=pytyping_modifiers)
if is_cdef:
entry.is_cglobal = 1
if entry.type.declaration_value:
@@ -1454,7 +1665,7 @@ class ModuleScope(Scope):
entry = self.lookup_here(name)
if entry and entry.defined_in_pxd:
if entry.visibility != "private":
- mangled_cname = self.mangle(Naming.var_prefix, name)
+ mangled_cname = self.mangle(Naming.func_prefix, name)
if entry.cname == mangled_cname:
cname = name
entry.cname = cname
@@ -1505,7 +1716,7 @@ class ModuleScope(Scope):
if entry and not shadow:
type = entry.type
if not (entry.is_type and type.is_extension_type):
- entry = None # Will cause redeclaration and produce an error
+ entry = None # Will cause redeclaration and produce an error
else:
scope = type.scope
if typedef_flag and (not scope or scope.defined):
@@ -1551,7 +1762,8 @@ class ModuleScope(Scope):
if not type.scope:
if defining or implementing:
scope = CClassScope(name = name, outer_scope = self,
- visibility = visibility)
+ visibility=visibility,
+ parent_type=type)
scope.directives = self.directives.copy()
if base_type and base_type.scope:
scope.declare_inherited_c_attributes(base_type.scope)
@@ -1585,6 +1797,15 @@ class ModuleScope(Scope):
if self.directives.get('final'):
entry.type.is_final_type = True
+ collection_type = self.directives.get('collection_type')
+ if collection_type:
+ from .UtilityCode import NonManglingModuleScope
+ if not isinstance(self, NonManglingModuleScope):
+ # TODO - DW would like to make it public, but I'm making it internal-only
+ # for now to avoid adding new features without consensus
+ error(pos, "'collection_type' is not a public cython directive")
+ if collection_type == 'sequence':
+ entry.type.has_sequence_flag = True
# cdef classes are always exported, but we need to set it to
# distinguish between unused Cython utility code extension classes
@@ -1727,6 +1948,7 @@ class ModuleScope(Scope):
class LocalScope(Scope):
+ is_local_scope = True
# Does the function have a 'with gil:' block?
has_with_gil_block = False
@@ -1740,10 +1962,11 @@ class LocalScope(Scope):
Scope.__init__(self, name, outer_scope, parent_scope)
def mangle(self, prefix, name):
- return prefix + name
+ return punycodify_name(prefix + name)
def declare_arg(self, name, type, pos):
# Add an entry for an argument of a function.
+ name = self.mangle_class_private_name(name)
cname = self.mangle(Naming.var_prefix, name)
entry = self.declare(name, cname, type, pos, 'private')
entry.is_variable = 1
@@ -1755,14 +1978,15 @@ class LocalScope(Scope):
return entry
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None):
+ name = self.mangle_class_private_name(name)
# Add an entry for a local variable.
if visibility in ('public', 'readonly'):
error(pos, "Local variable cannot be declared %s" % visibility)
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility,
- api=api, in_pxd=in_pxd, is_cdef=is_cdef)
+ api=api, in_pxd=in_pxd, is_cdef=is_cdef, pytyping_modifiers=pytyping_modifiers)
if entry.type.declaration_value:
entry.init = entry.type.declaration_value
entry.is_local = 1
@@ -1790,24 +2014,28 @@ class LocalScope(Scope):
if entry is None or not entry.from_closure:
error(pos, "no binding for nonlocal '%s' found" % name)
+ def _create_inner_entry_for_closure(self, name, entry):
+ entry.in_closure = True
+ inner_entry = InnerEntry(entry, self)
+ inner_entry.is_variable = True
+ self.entries[name] = inner_entry
+ return inner_entry
+
def lookup(self, name):
# Look up name in this scope or an enclosing one.
# Return None if not found.
+
entry = Scope.lookup(self, name)
if entry is not None:
entry_scope = entry.scope
- while entry_scope.is_genexpr_scope:
+ while entry_scope.is_comprehension_scope:
entry_scope = entry_scope.outer_scope
if entry_scope is not self and entry_scope.is_closure_scope:
if hasattr(entry.scope, "scope_class"):
raise InternalError("lookup() after scope class created.")
# The actual c fragment for the different scopes differs
# on the outside and inside, so we make a new entry
- entry.in_closure = True
- inner_entry = InnerEntry(entry, self)
- inner_entry.is_variable = True
- self.entries[name] = inner_entry
- return inner_entry
+ return self._create_inner_entry_for_closure(name, entry)
return entry
def mangle_closure_cnames(self, outer_scope_cname):
@@ -1824,19 +2052,21 @@ class LocalScope(Scope):
elif entry.in_closure:
entry.original_cname = entry.cname
entry.cname = "%s->%s" % (Naming.cur_scope_cname, entry.cname)
+ if entry.type.is_cpp_class and entry.scope.directives['cpp_locals']:
+ entry.make_cpp_optional()
-class GeneratorExpressionScope(Scope):
- """Scope for generator expressions and comprehensions. As opposed
- to generators, these can be easily inlined in some cases, so all
+class ComprehensionScope(Scope):
+ """Scope for comprehensions (but not generator expressions, which use ClosureScope).
+ As opposed to generators, these can be easily inlined in some cases, so all
we really need is a scope that holds the loop variable(s).
"""
- is_genexpr_scope = True
+ is_comprehension_scope = True
def __init__(self, outer_scope):
parent_scope = outer_scope
# TODO: also ignore class scopes?
- while parent_scope.is_genexpr_scope:
+ while parent_scope.is_comprehension_scope:
parent_scope = parent_scope.parent_scope
name = parent_scope.global_scope().next_id(Naming.genexpr_id_ref)
Scope.__init__(self, name, outer_scope, parent_scope)
@@ -1845,7 +2075,7 @@ class GeneratorExpressionScope(Scope):
# Class/ExtType scopes are filled at class creation time, i.e. from the
# module init function or surrounding function.
- while outer_scope.is_genexpr_scope or outer_scope.is_c_class_scope or outer_scope.is_py_class_scope:
+ while outer_scope.is_comprehension_scope or outer_scope.is_c_class_scope or outer_scope.is_py_class_scope:
outer_scope = outer_scope.outer_scope
self.var_entries = outer_scope.var_entries # keep declarations outside
outer_scope.subscopes.add(self)
@@ -1854,13 +2084,14 @@ class GeneratorExpressionScope(Scope):
return '%s%s' % (self.genexp_prefix, self.parent_scope.mangle(prefix, name))
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = True):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=True, pytyping_modifiers=None):
if type is unspecified_type:
# if the outer scope defines a type for this variable, inherit it
outer_entry = self.outer_scope.lookup(name)
if outer_entry and outer_entry.is_variable:
- type = outer_entry.type # may still be 'unspecified_type' !
+ type = outer_entry.type # may still be 'unspecified_type' !
+ self._reject_pytyping_modifiers(pos, pytyping_modifiers)
# the parent scope needs to generate code for the variable, but
# this scope must hold its name exclusively
cname = '%s%s' % (self.genexp_prefix, self.parent_scope.mangle(Naming.var_prefix, name or self.next_id()))
@@ -1875,6 +2106,10 @@ class GeneratorExpressionScope(Scope):
self.entries[name] = entry
return entry
+ def declare_assignment_expression_target(self, name, type, pos):
+ # should be declared in the parent scope instead
+ return self.parent_scope.declare_var(name, type, pos)
+
def declare_pyfunction(self, name, pos, allow_redefine=False):
return self.outer_scope.declare_pyfunction(
name, pos, allow_redefine)
@@ -1885,6 +2120,12 @@ class GeneratorExpressionScope(Scope):
def add_lambda_def(self, def_node):
return self.outer_scope.add_lambda_def(def_node)
+ def lookup_assignment_expression_target(self, name):
+ entry = self.lookup_here(name)
+ if not entry:
+ entry = self.parent_scope.lookup_assignment_expression_target(name)
+ return entry
+
class ClosureScope(LocalScope):
@@ -1906,17 +2147,36 @@ class ClosureScope(LocalScope):
def declare_pyfunction(self, name, pos, allow_redefine=False):
return LocalScope.declare_pyfunction(self, name, pos, allow_redefine, visibility='private')
+ def declare_assignment_expression_target(self, name, type, pos):
+ return self.declare_var(name, type, pos)
+
+
+class GeneratorExpressionScope(ClosureScope):
+ is_generator_expression_scope = True
+
+ def declare_assignment_expression_target(self, name, type, pos):
+ entry = self.parent_scope.declare_var(name, type, pos)
+ return self._create_inner_entry_for_closure(name, entry)
+
+ def lookup_assignment_expression_target(self, name):
+ entry = self.lookup_here(name)
+ if not entry:
+ entry = self.parent_scope.lookup_assignment_expression_target(name)
+ if entry:
+ return self._create_inner_entry_for_closure(name, entry)
+ return entry
+
class StructOrUnionScope(Scope):
# Namespace of a C struct or union.
def __init__(self, name="?"):
- Scope.__init__(self, name, None, None)
+ Scope.__init__(self, name, outer_scope=None, parent_scope=None)
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0,
- allow_pyobject=False, allow_memoryview=False):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None,
+ allow_pyobject=False, allow_memoryview=False, allow_refcounted=False):
# Add an entry for an attribute.
if not cname:
cname = name
@@ -1924,16 +2184,20 @@ class StructOrUnionScope(Scope):
cname = c_safe_identifier(cname)
if type.is_cfunction:
type = PyrexTypes.CPtrType(type)
+ self._reject_pytyping_modifiers(pos, pytyping_modifiers)
entry = self.declare(name, cname, type, pos, visibility)
entry.is_variable = 1
self.var_entries.append(entry)
- if type.is_pyobject and not allow_pyobject:
- error(pos, "C struct/union member cannot be a Python object")
- elif type.is_memoryviewslice and not allow_memoryview:
- # Memory views wrap their buffer owner as a Python object.
- error(pos, "C struct/union member cannot be a memory view")
- if visibility != 'private':
- error(pos, "C struct/union member cannot be declared %s" % visibility)
+ if type.is_pyobject:
+ if not allow_pyobject:
+ error(pos, "C struct/union member cannot be a Python object")
+ elif type.is_memoryviewslice:
+ if not allow_memoryview:
+ # Memory views wrap their buffer owner as a Python object.
+ error(pos, "C struct/union member cannot be a memory view")
+ elif type.needs_refcounting:
+ if not allow_refcounted:
+ error(pos, "C struct/union member cannot be reference-counted type '%s'" % type)
return entry
def declare_cfunction(self, name, type, pos,
@@ -1954,6 +2218,16 @@ class ClassScope(Scope):
# declared in the class
# doc string or None Doc string
+ scope_predefined_names = ['__module__', '__qualname__']
+
+ def mangle_class_private_name(self, name):
+ # a few utilitycode names need to specifically be ignored
+ if name and name.lower().startswith("__pyx_"):
+ return name
+ if name and name.startswith('__') and not name.endswith('__'):
+ name = EncodedString('_%s%s' % (self.class_name.lstrip('_'), name))
+ return name
+
def __init__(self, name, outer_scope):
Scope.__init__(self, name, outer_scope, outer_scope)
self.class_name = name
@@ -1987,28 +2261,16 @@ class PyClassScope(ClassScope):
is_py_class_scope = 1
- def mangle_class_private_name(self, name):
- return self.mangle_special_name(name)
-
- def mangle_special_name(self, name):
- if name and name.startswith('__') and not name.endswith('__'):
- name = EncodedString('_%s%s' % (self.class_name.lstrip('_'), name))
- return name
-
- def lookup_here(self, name):
- name = self.mangle_special_name(name)
- return ClassScope.lookup_here(self, name)
-
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0):
- name = self.mangle_special_name(name)
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None):
+ name = self.mangle_class_private_name(name)
if type is unspecified_type:
type = py_object_type
# Add an entry for a class attribute.
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility,
- api=api, in_pxd=in_pxd, is_cdef=is_cdef)
+ api=api, in_pxd=in_pxd, is_cdef=is_cdef, pytyping_modifiers=pytyping_modifiers)
entry.is_pyglobal = 1
entry.is_pyclass_attr = 1
return entry
@@ -2043,7 +2305,7 @@ class PyClassScope(ClassScope):
class CClassScope(ClassScope):
# Namespace of an extension type.
#
- # parent_type CClassType
+ # parent_type PyExtensionType
# #typeobj_cname string or None
# #objstruct_cname string
# method_table_cname string
@@ -2062,18 +2324,26 @@ class CClassScope(ClassScope):
has_pyobject_attrs = False
has_memoryview_attrs = False
- has_cpp_class_attrs = False
+ has_cpp_constructable_attrs = False
has_cyclic_pyobject_attrs = False
defined = False
implemented = False
- def __init__(self, name, outer_scope, visibility):
+ def __init__(self, name, outer_scope, visibility, parent_type):
ClassScope.__init__(self, name, outer_scope)
if visibility != 'extern':
self.method_table_cname = outer_scope.mangle(Naming.methtab_prefix, name)
self.getset_table_cname = outer_scope.mangle(Naming.gstab_prefix, name)
self.property_entries = []
self.inherited_var_entries = []
+ self.parent_type = parent_type
+ # Usually parent_type will be an extension type and so the typeptr_cname
+ # can be used to calculate the namespace_cname. Occasionally other types
+ # are used (e.g. numeric/complex types) and in these cases the typeptr
+ # isn't relevant.
+ if ((parent_type.is_builtin_type or parent_type.is_extension_type)
+ and parent_type.typeptr_cname):
+ self.namespace_cname = "(PyObject *)%s" % parent_type.typeptr_cname
def needs_gc(self):
# If the type or any of its base types have Python-valued
@@ -2087,6 +2357,22 @@ class CClassScope(ClassScope):
return not self.parent_type.is_gc_simple
return False
+ def needs_trashcan(self):
+ # If the trashcan directive is explicitly set to False,
+ # unconditionally disable the trashcan.
+ directive = self.directives.get('trashcan')
+ if directive is False:
+ return False
+ # If the directive is set to True and the class has Python-valued
+ # C attributes, then it should use the trashcan in tp_dealloc.
+ if directive and self.has_cyclic_pyobject_attrs:
+ return True
+ # Use the trashcan if the base class uses it
+ base_type = self.parent_type.base_type
+ if base_type and base_type.scope is not None:
+ return base_type.scope.needs_trashcan()
+ return self.parent_type.builtin_trashcan
+
def needs_tp_clear(self):
"""
Do we need to generate an implementation for the tp_clear slot? Can
@@ -2094,6 +2380,25 @@ class CClassScope(ClassScope):
"""
return self.needs_gc() and not self.directives.get('no_gc_clear', False)
+ def may_have_finalize(self):
+ """
+ This covers cases where we definitely have a __del__ function
+ and also cases where one of the base classes could have a __del__
+ function but we don't know.
+ """
+ current_type_scope = self
+ while current_type_scope:
+ del_entry = current_type_scope.lookup_here("__del__")
+ if del_entry and del_entry.is_special:
+ return True
+ if (current_type_scope.parent_type.is_external or not current_type_scope.implemented or
+ current_type_scope.parent_type.multiple_bases):
+ # we don't know if we have __del__, so assume we do and call it
+ return True
+ current_base_type = current_type_scope.parent_type.base_type
+ current_type_scope = current_base_type.scope if current_base_type else None
+ return False
+
def get_refcounted_entries(self, include_weakref=False,
include_gc_simple=True):
py_attrs = []
@@ -2114,15 +2419,30 @@ class CClassScope(ClassScope):
return have_entries, (py_attrs, py_buffers, memoryview_slices)
def declare_var(self, name, type, pos,
- cname = None, visibility = 'private',
- api = 0, in_pxd = 0, is_cdef = 0):
+ cname=None, visibility='private',
+ api=False, in_pxd=False, is_cdef=False, pytyping_modifiers=None):
+ name = self.mangle_class_private_name(name)
+
+ if pytyping_modifiers:
+ if "typing.ClassVar" in pytyping_modifiers:
+ is_cdef = 0
+ if not type.is_pyobject:
+ if not type.equivalent_type:
+ warning(pos, "ClassVar[] requires the type to be a Python object type. Found '%s', using object instead." % type)
+ type = py_object_type
+ else:
+ type = type.equivalent_type
+ if "dataclasses.InitVar" in pytyping_modifiers and not self.is_c_dataclass_scope:
+ error(pos, "Use of cython.dataclasses.InitVar does not make sense outside a dataclass")
+
if is_cdef:
# Add an entry for an attribute.
if self.defined:
error(pos,
"C attributes cannot be added in implementation part of"
" extension type defined in a pxd")
- if not self.is_closure_class_scope and get_special_method_signature(name):
+ if (not self.is_closure_class_scope and
+ get_slot_table(self.directives).get_special_method_signature(name)):
error(pos,
"The name '%s' is reserved for a special method."
% name)
@@ -2130,16 +2450,21 @@ class CClassScope(ClassScope):
cname = name
if visibility == 'private':
cname = c_safe_identifier(cname)
- if type.is_cpp_class and visibility != 'extern':
- type.check_nullary_constructor(pos)
- self.use_utility_code(Code.UtilityCode("#include <new>"))
+ cname = punycodify_name(cname, Naming.unicode_structmember_prefix)
entry = self.declare(name, cname, type, pos, visibility)
entry.is_variable = 1
self.var_entries.append(entry)
+ entry.pytyping_modifiers = pytyping_modifiers
+ if type.is_cpp_class and visibility != 'extern':
+ if self.directives['cpp_locals']:
+ entry.make_cpp_optional()
+ else:
+ type.check_nullary_constructor(pos)
if type.is_memoryviewslice:
self.has_memoryview_attrs = True
- elif type.is_cpp_class:
- self.has_cpp_class_attrs = True
+ elif type.needs_cpp_construction:
+ self.use_utility_code(Code.UtilityCode("#include <new>"))
+ self.has_cpp_constructable_attrs = True
elif type.is_pyobject and (self.is_closure_class_scope or name != '__weakref__'):
self.has_pyobject_attrs = True
if (not type.is_builtin_type
@@ -2167,12 +2492,12 @@ class CClassScope(ClassScope):
# Add an entry for a class attribute.
entry = Scope.declare_var(self, name, type, pos,
cname=cname, visibility=visibility,
- api=api, in_pxd=in_pxd, is_cdef=is_cdef)
+ api=api, in_pxd=in_pxd, is_cdef=is_cdef, pytyping_modifiers=pytyping_modifiers)
entry.is_member = 1
- entry.is_pyglobal = 1 # xxx: is_pyglobal changes behaviour in so many places that
- # I keep it in for now. is_member should be enough
- # later on
- self.namespace_cname = "(PyObject *)%s" % self.parent_type.typeptr_cname
+ # xxx: is_pyglobal changes behaviour in so many places that I keep it in for now.
+ # is_member should be enough later on
+ entry.is_pyglobal = 1
+
return entry
def declare_pyfunction(self, name, pos, allow_redefine=False):
@@ -2189,7 +2514,7 @@ class CClassScope(ClassScope):
"in a future version of Pyrex and Cython. Use __cinit__ instead.")
entry = self.declare_var(name, py_object_type, pos,
visibility='extern')
- special_sig = get_special_method_signature(name)
+ special_sig = get_slot_table(self.directives).get_special_method_signature(name)
if special_sig:
# Special methods get put in the method table with a particular
# signature declared in advance.
@@ -2220,7 +2545,9 @@ class CClassScope(ClassScope):
def declare_cfunction(self, name, type, pos,
cname=None, visibility='private', api=0, in_pxd=0,
defining=0, modifiers=(), utility_code=None, overridable=False):
- if get_special_method_signature(name) and not self.parent_type.is_builtin_type:
+ name = self.mangle_class_private_name(name)
+ if (get_slot_table(self.directives).get_special_method_signature(name)
+ and not self.parent_type.is_builtin_type):
error(pos, "Special methods must be declared with 'def', not 'cdef'")
args = type.args
if not type.is_static_method:
@@ -2231,10 +2558,11 @@ class CClassScope(ClassScope):
(args[0].type, name, self.parent_type))
entry = self.lookup_here(name)
if cname is None:
- cname = c_safe_identifier(name)
+ cname = punycodify_name(c_safe_identifier(name), Naming.unicode_vtabentry_prefix)
if entry:
if not entry.is_cfunction:
- warning(pos, "'%s' redeclared " % name, 0)
+ error(pos, "'%s' redeclared " % name)
+ entry.already_declared_here()
else:
if defining and entry.func_cname:
error(pos, "'%s' already defined" % name)
@@ -2246,13 +2574,14 @@ class CClassScope(ClassScope):
entry.type = entry.type.with_with_gil(type.with_gil)
elif type.compatible_signature_with(entry.type, as_cmethod = 1) and type.nogil == entry.type.nogil:
if (self.defined and not in_pxd
- and not type.same_c_signature_as_resolved_type(entry.type, as_cmethod = 1, as_pxd_definition = 1)):
+ and not type.same_c_signature_as_resolved_type(
+ entry.type, as_cmethod=1, as_pxd_definition=1)):
# TODO(robertwb): Make this an error.
warning(pos,
"Compatible but non-identical C method '%s' not redeclared "
"in definition part of extension type '%s'. "
"This may cause incorrect vtables to be generated." % (
- name, self.class_name), 2)
+ name, self.class_name), 2)
warning(entry.pos, "Previous declaration is here", 2)
entry = self.add_cfunction(name, type, pos, cname, visibility='ignore', modifiers=modifiers)
else:
@@ -2272,18 +2601,20 @@ class CClassScope(ClassScope):
if u'inline' in modifiers:
entry.is_inline_cmethod = True
- if (self.parent_type.is_final_type or entry.is_inline_cmethod or
- self.directives.get('final')):
+ if self.parent_type.is_final_type or entry.is_inline_cmethod or self.directives.get('final'):
entry.is_final_cmethod = True
entry.final_func_cname = entry.func_cname
+ if not type.is_fused:
+ entry.vtable_type = entry.type
+ entry.type = type
return entry
def add_cfunction(self, name, type, pos, cname, visibility, modifiers, inherited=False):
# Add a cfunction entry without giving it a func_cname.
prev_entry = self.lookup_here(name)
- entry = ClassScope.add_cfunction(self, name, type, pos, cname,
- visibility, modifiers, inherited=inherited)
+ entry = ClassScope.add_cfunction(
+ self, name, type, pos, cname, visibility, modifiers, inherited=inherited)
entry.is_cmethod = 1
entry.prev_entry = prev_entry
return entry
@@ -2292,8 +2623,8 @@ class CClassScope(ClassScope):
# overridden methods of builtin types still have their Python
# equivalent that must be accessible to support bound methods
name = EncodedString(name)
- entry = self.declare_cfunction(name, type, None, cname, visibility='extern',
- utility_code=utility_code)
+ entry = self.declare_cfunction(
+ name, type, pos=None, cname=cname, visibility='extern', utility_code=utility_code)
var_entry = Entry(name, name, py_object_type)
var_entry.qualified_name = name
var_entry.is_variable = 1
@@ -2303,18 +2634,44 @@ class CClassScope(ClassScope):
entry.as_variable = var_entry
return entry
- def declare_property(self, name, doc, pos):
+ def declare_property(self, name, doc, pos, ctype=None, property_scope=None):
entry = self.lookup_here(name)
if entry is None:
- entry = self.declare(name, name, py_object_type, pos, 'private')
- entry.is_property = 1
+ entry = self.declare(name, name, py_object_type if ctype is None else ctype, pos, 'private')
+ entry.is_property = True
+ if ctype is not None:
+ entry.is_cproperty = True
entry.doc = doc
- entry.scope = PropertyScope(name,
- outer_scope = self.global_scope(), parent_scope = self)
- entry.scope.parent_type = self.parent_type
+ if property_scope is None:
+ entry.scope = PropertyScope(name, class_scope=self)
+ else:
+ entry.scope = property_scope
self.property_entries.append(entry)
return entry
+ def declare_cproperty(self, name, type, cfunc_name, doc=None, pos=None, visibility='extern',
+ nogil=False, with_gil=False, exception_value=None, exception_check=False,
+ utility_code=None):
+ """Internal convenience method to declare a C property function in one go.
+ """
+ property_entry = self.declare_property(name, doc=doc, ctype=type, pos=pos)
+ cfunc_entry = property_entry.scope.declare_cfunction(
+ name=name,
+ type=PyrexTypes.CFuncType(
+ type,
+ [PyrexTypes.CFuncTypeArg("self", self.parent_type, pos=None)],
+ nogil=nogil,
+ with_gil=with_gil,
+ exception_value=exception_value,
+ exception_check=exception_check,
+ ),
+ cname=cfunc_name,
+ utility_code=utility_code,
+ visibility=visibility,
+ pos=pos,
+ )
+ return property_entry, cfunc_entry
+
def declare_inherited_c_attributes(self, base_scope):
# Declare entries for all the C attributes of an
# inherited type, with cnames modified appropriately
@@ -2328,6 +2685,8 @@ class CClassScope(ClassScope):
base_entry.name, adapt(base_entry.cname),
base_entry.type, None, 'private')
entry.is_variable = 1
+ entry.is_inherited = True
+ entry.annotation = base_entry.annotation
self.inherited_var_entries.append(entry)
# If the class defined in a pxd, specific entries have not been added.
@@ -2343,9 +2702,9 @@ class CClassScope(ClassScope):
is_builtin = var_entry and var_entry.is_builtin
if not is_builtin:
cname = adapt(cname)
- entry = self.add_cfunction(base_entry.name, base_entry.type,
- base_entry.pos, cname,
- base_entry.visibility, base_entry.func_modifiers, inherited=True)
+ entry = self.add_cfunction(
+ base_entry.name, base_entry.type, base_entry.pos, cname,
+ base_entry.visibility, base_entry.func_modifiers, inherited=True)
entry.is_inherited = 1
if base_entry.is_final_cmethod:
entry.is_final_cmethod = True
@@ -2379,25 +2738,29 @@ class CppClassScope(Scope):
template_entry.is_type = 1
def declare_var(self, name, type, pos,
- cname = None, visibility = 'extern',
- api = 0, in_pxd = 0, is_cdef = 0, defining = 0):
+ cname=None, visibility='extern',
+ api=False, in_pxd=False, is_cdef=False, defining=False, pytyping_modifiers=None):
# Add an entry for an attribute.
if not cname:
cname = name
+ self._reject_pytyping_modifiers(pos, pytyping_modifiers)
entry = self.lookup_here(name)
if defining and entry is not None:
- if entry.type.same_as(type):
+ if type.is_cfunction:
+ entry = self.declare(name, cname, type, pos, visibility)
+ elif entry.type.same_as(type):
# Fix with_gil vs nogil.
entry.type = entry.type.with_with_gil(type.with_gil)
- elif type.is_cfunction and type.compatible_signature_with(entry.type):
- entry.type = type
else:
error(pos, "Function signature does not match previous declaration")
else:
entry = self.declare(name, cname, type, pos, visibility)
+ if type.is_cfunction and not defining:
+ entry.is_inherited = 1
entry.is_variable = 1
- if type.is_cfunction and self.type:
- if not self.type.get_fused_types():
+ if type.is_cfunction:
+ entry.is_cfunction = 1
+ if self.type and not self.type.get_fused_types():
entry.func_cname = "%s::%s" % (self.type.empty_declaration_code(), cname)
if name != "this" and (defining or name != "<init>"):
self.var_entries.append(entry)
@@ -2409,7 +2772,7 @@ class CppClassScope(Scope):
class_name = self.name.split('::')[-1]
if name in (class_name, '__init__') and cname is None:
cname = "%s__init__%s" % (Naming.func_prefix, class_name)
- name = '<init>'
+ name = EncodedString('<init>')
type.return_type = PyrexTypes.CVoidType()
# This is called by the actual constructor, but need to support
# arguments that cannot by called by value.
@@ -2423,7 +2786,7 @@ class CppClassScope(Scope):
type.args = [maybe_ref(arg) for arg in type.args]
elif name == '__dealloc__' and cname is None:
cname = "%s__dealloc__%s" % (Naming.func_prefix, class_name)
- name = '<del>'
+ name = EncodedString('<del>')
type.return_type = PyrexTypes.CVoidType()
if name in ('<init>', '<del>') and type.nogil:
for base in self.type.base_classes:
@@ -2431,12 +2794,10 @@ class CppClassScope(Scope):
if base_entry and not base_entry.type.nogil:
error(pos, "Constructor cannot be called without GIL unless all base constructors can also be called without GIL")
error(base_entry.pos, "Base constructor defined here.")
- prev_entry = self.lookup_here(name)
+ # The previous entries management is now done directly in Scope.declare
entry = self.declare_var(name, type, pos,
defining=defining,
cname=cname, visibility=visibility)
- if prev_entry and not defining:
- entry.overloaded_alternatives = prev_entry.all_alternatives()
entry.utility_code = utility_code
type.entry = entry
return entry
@@ -2453,19 +2814,21 @@ class CppClassScope(Scope):
# Declare entries for all the C++ attributes of an
# inherited type, with cnames modified appropriately
# to work with this type.
- for base_entry in \
- base_scope.inherited_var_entries + base_scope.var_entries:
- #constructor/destructor is not inherited
- if base_entry.name in ("<init>", "<del>"):
- continue
- #print base_entry.name, self.entries
- if base_entry.name in self.entries:
- base_entry.name # FIXME: is there anything to do in this case?
- entry = self.declare(base_entry.name, base_entry.cname,
- base_entry.type, None, 'extern')
- entry.is_variable = 1
- entry.is_inherited = 1
- self.inherited_var_entries.append(entry)
+ for base_entry in base_scope.inherited_var_entries + base_scope.var_entries:
+ #constructor/destructor is not inherited
+ if base_entry.name in ("<init>", "<del>"):
+ continue
+ #print base_entry.name, self.entries
+ if base_entry.name in self.entries:
+ base_entry.name # FIXME: is there anything to do in this case?
+ entry = self.declare(base_entry.name, base_entry.cname,
+ base_entry.type, None, 'extern')
+ entry.is_variable = 1
+ entry.is_inherited = 1
+ if base_entry.is_cfunction:
+ entry.is_cfunction = 1
+ entry.func_cname = base_entry.func_cname
+ self.inherited_var_entries.append(entry)
for base_entry in base_scope.cfunc_entries:
entry = self.declare_cfunction(base_entry.name, base_entry.type,
base_entry.pos, base_entry.cname,
@@ -2477,7 +2840,7 @@ class CppClassScope(Scope):
if base_entry.name not in base_templates:
entry = self.declare_type(base_entry.name, base_entry.type,
base_entry.pos, base_entry.cname,
- base_entry.visibility)
+ base_entry.visibility, defining=False)
entry.is_inherited = 1
def specialize(self, values, type_entry):
@@ -2506,6 +2869,30 @@ class CppClassScope(Scope):
return scope
+ def lookup_here(self, name):
+ if name == "__init__":
+ name = "<init>"
+ elif name == "__dealloc__":
+ name = "<del>"
+ return super(CppClassScope, self).lookup_here(name)
+
+
+class CppScopedEnumScope(Scope):
+ # Namespace of a ScopedEnum
+
+ def __init__(self, name, outer_scope):
+ Scope.__init__(self, name, outer_scope, None)
+
+ def declare_var(self, name, type, pos,
+ cname=None, visibility='extern', pytyping_modifiers=None):
+ # Add an entry for an attribute.
+ if not cname:
+ cname = name
+ self._reject_pytyping_modifiers(pos, pytyping_modifiers)
+ entry = self.declare(name, cname, type, pos, visibility)
+ entry.is_variable = True
+ return entry
+
class PropertyScope(Scope):
# Scope holding the __get__, __set__ and __del__ methods for
@@ -2515,6 +2902,31 @@ class PropertyScope(Scope):
is_property_scope = 1
+ def __init__(self, name, class_scope):
+ # outer scope is None for some internal properties
+ outer_scope = class_scope.global_scope() if class_scope.outer_scope else None
+ Scope.__init__(self, name, outer_scope, parent_scope=class_scope)
+ self.parent_type = class_scope.parent_type
+ self.directives = class_scope.directives
+
+ def declare_cfunction(self, name, type, pos, *args, **kwargs):
+ """Declare a C property function.
+ """
+ if type.return_type.is_void:
+ error(pos, "C property method cannot return 'void'")
+
+ if type.args and type.args[0].type is py_object_type:
+ # Set 'self' argument type to extension type.
+ type.args[0].type = self.parent_scope.parent_type
+ elif len(type.args) != 1:
+ error(pos, "C property method must have a single (self) argument")
+ elif not (type.args[0].type.is_pyobject or type.args[0].type is self.parent_scope.parent_type):
+ error(pos, "C property method must have a single (object) argument")
+
+ entry = Scope.declare_cfunction(self, name, type, pos, *args, **kwargs)
+ entry.is_cproperty = True
+ return entry
+
def declare_pyfunction(self, name, pos, allow_redefine=False):
# Add an entry for a method.
signature = get_property_accessor_signature(name)
@@ -2529,23 +2941,27 @@ class PropertyScope(Scope):
return None
-class CConstScope(Scope):
+class CConstOrVolatileScope(Scope):
- def __init__(self, const_base_type_scope):
+ def __init__(self, base_type_scope, is_const=0, is_volatile=0):
Scope.__init__(
self,
- 'const_' + const_base_type_scope.name,
- const_base_type_scope.outer_scope,
- const_base_type_scope.parent_scope)
- self.const_base_type_scope = const_base_type_scope
+ 'cv_' + base_type_scope.name,
+ base_type_scope.outer_scope,
+ base_type_scope.parent_scope)
+ self.base_type_scope = base_type_scope
+ self.is_const = is_const
+ self.is_volatile = is_volatile
def lookup_here(self, name):
- entry = self.const_base_type_scope.lookup_here(name)
+ entry = self.base_type_scope.lookup_here(name)
if entry is not None:
entry = copy.copy(entry)
- entry.type = PyrexTypes.c_const_type(entry.type)
+ entry.type = PyrexTypes.c_const_or_volatile_type(
+ entry.type, self.is_const, self.is_volatile)
return entry
+
class TemplateScope(Scope):
def __init__(self, name, outer_scope):
Scope.__init__(self, name, outer_scope, None)