diff options
author | nik-bes <[email protected]> | 2025-05-19 07:20:13 +0300 |
---|---|---|
committer | nik-bes <[email protected]> | 2025-05-19 07:36:02 +0300 |
commit | 317b7368e24941ff76499f500579fd9b10f6656e (patch) | |
tree | abbcbaea595e7d2e9f23cf59a408b3082fe4340d /contrib/tools/cython/Cython/Compiler/Symtab.py | |
parent | 6b666a52d40308ab9b3532cd8d3008b9f37cfffb (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.py | 944 |
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) |