463 lines
16 KiB
Python
463 lines
16 KiB
Python
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
# Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>
|
|
|
|
|
|
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)
|