{% macro getter_func_template(field_name, get_variable_template) %}
def make_getter(type_def):
    '''
    Factory function that allows the metaclass to pass
    data into this context (like type definitions).
    '''
    def _get_{{field_name}}(self) -> type_def:
        '''
        Autogenerated code: Setter function for {{field_name}}.
        '''
        {{get_variable_template|format(key=field_name)|indent(8)}}
    return _get_{{field_name}}
{% endmacro %}

{% macro setter_func_template(field_name, setter_variable_template, on_sets=None, has_coercion=False, type_failure_func_names=None) %}
def make_setter(type_def, fast, derived, type_restriction, coerce_types=(), coerce_func=None):
    if isinstance(type_restriction, type):
        type_restriction = (type_restriction,)

    assert isinstance(type_restriction, tuple) and all(
        isinstance(cls, type) for cls in type_restriction), \
        'Not all types {!r} are cls'.format(type_restriction)

    if fast:
        def _set_{{field_name}}(self, val: type_def) -> NoneType:
            {%- if has_coercion %}
            if isinstance(val, coerce_types):
                val = coerce_func(val)
            {%- endif %}
            {%- if on_sets %}
            _old_value = self.{{field_name}}
            {%- endif %}
            {{setter_variable_template|format(key=field_name)|indent(12)}}
            {%- if on_sets %}
            {%- for on_func_name in on_sets %}
            self.{{on_func_name}}(_old_value, val)
            {%- endfor %}
            {%- endif %}
    else:
        def _set_{{field_name}}(self, val: type_def) -> NoneType:
            if not isinstance(val, type_restriction):
                if isinstance(val, dict) and derived is not None:
                    val = derived(**val)
                {%- if has_coercion %}
                if isinstance(val, coerce_types):
                    val = coerce_func(val)
                {%- endif %}
                if not isinstance(val, type_restriction):
                    {%- if type_failure_func_names %}
                    handled_error = (func(val) for func in (
                        {%- for on_error_func_name in type_failure_func_names %}
                        self.{{on_error_func_name}},
                        {%- endfor %}
                    ))
                    if not any(handled_error):
                        raise self._create_invalid_type(
                            '{{field_name}}',
                            val, type(val), type_restriction)
                    return
                    {%- else %}
                    raise self._create_invalid_type(
                        '{{field_name}}',
                        val, type(val), type_restriction)
                    {%- endif %}

            {%- if on_sets %}
            _old_value = self.{{field_name}}
            {%- endif %}
            {{setter_variable_template|format(key=field_name)|indent(12)}}
            {%- if on_sets %}
            {%- for on_func_name in on_sets %}
            self.{{on_func_name}}(_old_value, val)
            {%- endfor %}
            {%- endif %}

    return _set_{{field_name}}
{% endmacro %}

{% macro history_setter_variable_template(field_name, setter_variable_template) %}
if not self._flags:
    {{setter_variable_template|indent(4)}}
    return
old_value = self.{{field_name}}
{{setter_variable_template}}
self._record_change('{{field_name}}', old_value, val)
{% endmacro %}

{% macro make_eq_function(fields) %}
def __eq__(self, other):
    '''
    Autogenerated code: This represents a giant if-else chain to fast field comparison.
    '''
    # Assume structural subtyping!
    try:
        val = (
            {%- for field in fields %}
            self.{{field}} == other.{{field}},
            {%- endfor %}
        )
        return all(val)
    except AttributeError:
        pass
    # Defer upwards to whatevers in the MRO
    return super().__eq__(other)
{% endmacro %}
