# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from copy import copy from .scoped_name import ScopedName from .specification import (Argument, ArgumentType, FunctionCall, IfaceFileType, KwArgs, Signature, TypeHints, Value, ValueType) from .templates import (template_code, template_code_blocks, template_expansions, template_string) from .utils import append_iface_file, cached_name, normalised_scoped_name def instantiate_class(p, symbol, fq_cpp_name, tmpl_names, proto_class, template, py_name, no_type_name, docstring, pm): """ Instantiate a class template. """ # Create the expansions. expansions = template_expansions(tmpl_names, template.types) # Add a mapping from the template name to the instantiated name. expansions[str(proto_class.iface_file.fq_cpp_name)] = str(fq_cpp_name) # Create the new class starting with a shallow copy. i_class = copy(proto_class) if docstring is not None: i_class.docstring = docstring i_class.mro = [] i_class.virtual_overloads = [] i_class.visible_members = [] i_class.py_name = cached_name(pm.spec, py_name) i_class.template = template i_class.no_type_name = no_type_name # Handle the interface file. i_class.iface_file = pm.find_iface_file(p, symbol, fq_cpp_name, IfaceFileType.CLASS) i_class.iface_file.module = pm.module_state.module used = i_class.iface_file.used i_class.iface_file.type_header_code = template_code_blocks(pm.spec, used, proto_class.iface_file.type_header_code, expansions) # Make a copy of the used list and add the enclosing scope. for iface_file in proto_class.iface_file.used: append_iface_file(i_class.iface_file.used, iface_file) # Include any scope header code. i_class.scope = pm.scope if i_class.scope is not None: i_class.iface_file.type_header_code.extend( i_class.scope.iface_file.type_header_code) if pm.in_main_module: i_class.iface_file.cpp_name.used = True i_class.py_name.used = True # Handle any type hints. if proto_class.type_hints is not None: i_class.type_hints = instantiate_type_hints(pm.spec, proto_class.type_hints, expansions) # Handle any flagged enums. if proto_class.pyqt_flags_enums is not None: i_class.pyqt_flags_enums = [template_string(s, expansions) for s in proto_class.pyqt_flags_enums] # Handle the super-classes. i_class.superclasses = [] for superclass in proto_class.superclasses: superclass_name = superclass.iface_file.fq_cpp_name # Don't try and expand defined or scoped classes. if superclass.iface_file.module is not None or not superclass_name.is_simple: i_class.superclasses.append(superclass) continue for a, arg in enumerate(tmpl_names.args): if superclass_name.base_name == arg.definition.base_name: tmpl_arg = template.types.args[a] if tmpl_arg.type is ArgumentType.DEFINED: i_superclass = pm.find_class(p, symbol, IfaceFileType.CLASS, tmpl_arg.definition) elif tmpl_arg.type is ArgumentType.CLASS: i_superclass = tmpl_arg.definition else: pm.parser_error(p, symbol, "template argument '{0}' must expand to a class".format(superclass_name)) i_superclass = superclass i_class.superclasses.append(i_superclass) # Handle the enums. _instantiate_enums(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the variables. _instantiate_vars(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the typedefs. _instantiate_typedefs(p, symbol, tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the ctors. i_class.ctors = _instantiate_ctors(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the methods. i_class.members = _instantiate_methods(proto_class.members, i_class.iface_file.module, pm) i_class.overloads = _instantiate_overloads(proto_class.overloads, proto_class.members, i_class.members, tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the remaining handwritten code. i_class.bi_get_buffer_code = template_code(pm.spec, used, proto_class.bi_get_buffer_code, expansions) i_class.bi_release_buffer_code = template_code(pm.spec, used, proto_class.bi_release_buffer_code, expansions) i_class.convert_from_type_code = template_code(pm.spec, used, proto_class.convert_from_type_code, expansions) i_class.convert_to_subclass_code = template_code(pm.spec, used, proto_class.convert_to_subclass_code, expansions) i_class.convert_to_type_code = template_code(pm.spec, used, proto_class.convert_to_type_code, expansions) i_class.dealloc_code = template_code_blocks(pm.spec, used, proto_class.dealloc_code, expansions) i_class.dtor_virtual_catcher_code = template_code(pm.spec, used, proto_class.dtor_virtual_catcher_code, expansions) i_class.finalisation_code = template_code(pm.spec, used, proto_class.finalisation_code, expansions) i_class.gc_clear_code = template_code(pm.spec, used, proto_class.gc_clear_code, expansions) i_class.gc_traverse_code = template_code(pm.spec, used, proto_class.gc_traverse_code, expansions) i_class.instance_code = template_code(pm.spec, used, proto_class.instance_code, expansions) i_class.pickle_code = template_code(pm.spec, used, proto_class.pickle_code, expansions) i_class.type_code = template_code_blocks(pm.spec, used, proto_class.type_code, expansions) i_class.type_hint_code = template_code(pm.spec, used, proto_class.type_hint_code, expansions) pm.spec.classes.insert(0, i_class) def _instantiate_argument(proto_arg, proto_class, tmpl_names, template, i_class, expansions, pm): """ Return an instantiated Argument object. """ # Start with a shallow copy. i_arg = copy(proto_arg) # Descend into any sub-templates. if proto_arg.type is ArgumentType.TEMPLATE: proto_template = proto_arg.definition i_template = copy(proto_template) i_template.types = _instantiate_signature(proto_template.types, proto_class, tmpl_names, template, i_class, expansions, pm) i_arg.definition = i_template # Handle any default value. if proto_arg.default_value is not None: i_arg.default_value = [_instantiate_value(v, expansions) for v in proto_arg.default_value] # Handle any type hints. if proto_arg.type_hints is not None: i_arg.type_hints = instantiate_type_hints(pm.spec, proto_arg.type_hints, expansions) # Handle arguments that are unscoped names. if proto_arg.type is ArgumentType.DEFINED and proto_arg.definition.is_simple: name = proto_arg.definition.base_name for a, arg in enumerate(tmpl_names.args): if name == arg.definition.base_name: tad = template.types.args[a] i_arg.type = tad.type i_arg.definition = tad.definition # We take the constrained flag from the real type. i_arg.is_constrained = tad.is_constrained break else: # Handle the class name itself. if name == proto_class.iface_file.fq_cpp_name.base_name: i_arg.type = ArgumentType.CLASS i_arg.definition = i_class i_arg.original_type = None return i_arg def _instantiate_ctors(tmpl_names, proto_class, template, i_class, expansions, pm): """ Return a list of the instantiated ctors of a template class. """ i_ctors = [] used = i_class.iface_file.used for proto_ctor in proto_class.ctors: # Start with a shallow copy. i_ctor = copy(proto_ctor) i_ctor.py_signature = _instantiate_signature(proto_ctor.py_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=proto_ctor.kw_args) if proto_ctor.cpp_signature is proto_ctor.py_signature: i_ctor.cpp_signature = i_ctor.py_signature else: i_ctor.cpp_signature = _instantiate_signature( proto_ctor.cpp_signature.cpp_signature, proto_class, tmpl_names, template, i_class, expansions, pm) i_ctor.method_code = template_code(pm.spec, used, proto_ctor.method_code, expansions) i_ctor.premethod_code = template_code(pm.spec, used, proto_ctor.premethod_code, expansions) # Handle the default ctor. if proto_class.default_ctor is proto_ctor: i_class.default_ctor = i_ctor i_ctors.append(i_ctor) return i_ctors def _instantiate_enums(tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the enums for a template class. """ for proto_enum in list(pm.spec.enums): if proto_enum.scope is not proto_class: continue # Start with a shallow copy. i_enum = copy(proto_enum) if proto_enum.fq_cpp_name is not None: i_enum.fq_cpp_name = normalised_scoped_name(proto_enum.fq_cpp_name, i_class) i_enum.cached_fq_cpp_name = cached_name(pm.spec, str(i_enum.fq_cpp_name)) if pm.in_main_module: if i_enum.py_name is not None: i_enum.py_name = True if i_enum.cached_fq_cpp_name is not None: i_enum.cached_fq_cpp_name.used = True i_enum.scope = i_class i_enum.module = i_class.iface_file.module i_enum.members = [] for proto_member in proto_enum.members: # Start with a shallow copy. w_member = copy(proto_member) w_member.scope = i_enum i_enum.members.append(w_member) pm.spec.enums.insert(0, i_enum) def _instantiate_methods(proto_methods, target_module, pm): """ Return a list of the instantiated methods of a template class or enum. """ i_methods = [] for proto_method in proto_methods: # Start with a shallow copy. i_method = copy(proto_method) i_method.module = target_module if pm.in_main_module: i_method.py_name.used = True i_methods.append(i_method) return i_methods def _instantiate_overloads(proto_overloads, proto_methods, i_methods, tmpl_names, proto_class, template, i_class, expansions, pm): """ Return a list of the instantiated overloads of a template class or enum. """ i_overloads = [] used = i_class.iface_file.used for proto_overload in proto_overloads: # Start with a shallow copy. i_overload = copy(proto_overload) for i_method, proto_method in zip(i_methods, proto_methods): if proto_overload.common is proto_method: i_overload.common = i_method break i_overload.py_signature = _instantiate_signature( proto_overload.py_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=proto_overload.kw_args) if proto_overload.cpp_signature is proto_overload.py_signature: i_overload.cpp_signature = i_overload.py_signature else: i_overload.cpp_signature = _instantiate_signature( proto_overload.cpp_signature.cpp_signature, proto_class, tmpl_names, template, i_class, expansions, pm) i_overload.method_code = template_code(pm.spec, used, proto_overload.method_code, expansions) i_overload.premethod_code = template_code(pm.spec, used, proto_overload.premethod_code, expansions) i_overload.virtual_call_code = template_code(pm.spec, used, proto_overload.virtual_call_code, expansions) i_overload.virtual_catcher_code = template_code(pm.spec, used, proto_overload.virtual_catcher_code, expansions) i_overloads.append(i_overload) return i_overloads def _instantiate_signature(proto_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=KwArgs.NONE): """ Return an instantiated Signature object. """ i_signature = Signature() for proto_arg in proto_signature.args: i_arg = _instantiate_argument(proto_arg, proto_class, tmpl_names, template, i_class, expansions, pm) i_signature.args.append(i_arg) # Make sure we have the name of any keyword argument. if pm.in_main_module and i_arg.name is not None: if kw_args is KwArgs.ALL or (kw_args is KwArgs.OPTIONAL and i_arg.default_value is not None): i_arg.name.used = True if proto_signature.result is not None: i_signature.result = _instantiate_argument(proto_signature.result, proto_class, tmpl_names, template, i_class, expansions, pm) return i_signature def instantiate_type_hints(spec, proto_type_hints, expansions): """ Return an instantiated TypeHints object. """ if proto_type_hints.hint_in is not None: hint_in = template_string(proto_type_hints.hint_in, expansions, scope_replacement='.') else: hint_in = None if proto_type_hints.hint_out is not None: hint_out = template_string(proto_type_hints.hint_out, expansions, scope_replacement='.') else: hint_out = None return TypeHints(hint_in=hint_in, hint_out=hint_out, default_value=proto_type_hints.default_value) def _instantiate_typedefs(p, symbol, tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the typedefs of a template class. """ for proto_typedef in pm.spec.typedefs: if proto_typedef.scope is not proto_class: continue # Start with a shallow copy. i_typedef = copy(proto_typedef) i_typedef.fq_cpp_name = normalised_scoped_name( proto_typedef.fq_cpp_name, i_class) i_typedef.scope = i_class i_typedef.module = i_class.iface_file.module i_typedef.type = _instantiate_argument(proto_typedef.type, proto_class, tmpl_names, template, i_class, expansions, pm) pm.add_typedef(p, symbol, i_typedef) def _instantiate_value(proto_value, expansions): """ Return an instantiated Value object. """ # We only handle the subset where the value is an function call, ie. a # template ctor. i_value = proto_value if proto_value.value_type is ValueType.FCALL and proto_value.value.result.type is ArgumentType.DEFINED: proto_name = proto_value.value.result.definition if proto_name.is_simple: i_name = ScopedName.parse( template_string(proto_name.base_name, expansions)) i_result = Argument(type=ArgumentType.DEFINED, definition=i_name) i_fcall = FunctionCall(result=i_result, args=proto_value.value.args) i_value = Value(ValueType.FCALL, value=i_fcall) return i_value def _instantiate_vars(tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the enums for a template class. """ used = i_class.iface_file.used for proto_var in pm.spec.variables: if proto_var.scope is not proto_class: continue # Start with a shallow copy. i_var = copy(proto_var) if pm.in_main_module: i_var.py_name.used = True i_var.fq_cpp_name = normalised_scoped_name(proto_var.fq_cpp_name, i_class) i_var.scope = i_class i_var.module = i_class.iface_file.module i_var.type = _instantiate_argument(proto_var.type, proto_class, tmpl_names, template, i_class, expansions, pm) i_var.access_code = template_code(pm.spec, used, proto_var.access_code, expansions) i_var.get_code = template_code(pm.spec, used, proto_var.get_code, expansions) i_var.set_code = template_code(pm.spec, used, proto_var.set_code, expansions) pm.spec.variables.append(i_var)