def type_value_inference(self, overwrite_output=False):
        """
        Perform type inference and auto_val computation based on new input Vars
        in kwargs. If self._output_vars is None then we generate _output_vars;
        otherwise no new Var is created, but type inference result is verified
        against existing _output_vars, if overwrite_output is False.

        If overwrite_output is True, then the type inference result overwrites the
        existing _output_vars
        """
        output_types = self.type_inference()
        if not isinstance(output_types, tuple):
            output_types = (output_types,)
        output_vals = self._auto_val(output_types)
        try:
            output_names = self.output_names()
            if not isinstance(output_names, tuple):
                output_names = (output_names,)
        except NotImplementedError:
            if len(output_types) > 1:
                output_names = tuple(str(i) for i, _ in enumerate(output_types))
            else:
                output_names = ("",)  # output name same as op name.

        # Combine (output_names, output_types, output_vals) to create output
        # Vars.
        if self._output_vars is None:
            self._output_vars = []
            for i, (n, sym_type, sym_val) in enumerate(
                zip(output_names, output_types, output_vals)
            ):
                name = self.name + "_" + n if n != "" else self.name
                if types.is_list(sym_type):
                    new_var = ListVar(
                        name,
                        elem_type=sym_type.T[0],
                        init_length=sym_type.T[1],
                        dynamic_length=sym_type.T[2],
                        sym_val=sym_val
                        if (sym_val is not None and isinstance(sym_val.val, list))
                        else None,
                        op=self,
                        op_output_idx=i,
                    )
                    elem_shape = new_var.elem_shape
                    if elem_shape is not None and len(elem_shape) >= 5:
                        msg = (
                            "Core ML only supports list of elements with rank <= 4. "
                            'Layer "{}", with type "{}", outputs a list of rank {} tensors.'
                        ).format(self.name, self.op_type, len(elem_shape))
                        raise ValueError(msg)
                else:
                    if types.is_tensor(sym_type) and types.is_complex(sym_type.T[0]):
                        # Only `complex` and `const` ops need to maintain the real/imag data in the ComplexVar.
                        # For other ops, this ComplexVar is just a placeholder here, which will be
                        # replaced by a newly created ComplexVar during complex ops lowering pass.
                        if self.op_type == "complex":
                            real_data = self.real_data
                            imag_data = self.imag_data
                        elif self.op_type == "const":
                            real_data = np.real(sym_val.val)
                            imag_data = np.imag(sym_val.val)
                        else:
                            real_data = None
                            imag_data = None

                        new_var = ComplexVar(
                            name,
                            sym_type,
                            sym_val,
                            op=self,
                            op_output_idx=i,
                            real=real_data,
                            imag=imag_data,
                        )
                    else:
                        new_var = Var(name, sym_type, sym_val, op=self, op_output_idx=i)
                self._output_vars.append(new_var)
        else:
            # Check new inference result against existing self._output_vars.
            for i, (sym_type, sym_val) in enumerate(zip(output_types, output_vals)):
                out_var = self._output_vars[i]
                # Check type inference
                if overwrite_output:
                    out_var._sym_type = sym_type
                elif not types.is_compatible_type(sym_type, out_var.sym_type):
                    msg = "Output Var {} in op {} type changes with new input Vars"
                    raise ValueError(msg.format(out_var.name, self.name))

                # Check value inference
                if overwrite_output:
                    out_var._sym_val = sym_val

                if sym_val is not None and out_var.sym_val is not None:
                    if np.any(sym_val.val != out_var.sym_val):
                        if overwrite_output:
                            out_var._sym_val = sym_val
                        else:
                            msg = 'value_inference differs for var {} in op {}'
                            if not _is_compatible_symbolic_array(sym_val.val, out_var.sym_val):
                                raise ValueError(msg.format(out_var.name, self.name))

                for o in self.outputs:
                    o._set_nonreplaceable_vars_upstream()

        Perform type inference and auto_val computation based on new input Vars
        in kwargs. If self._output_vars is None then we generate _output_vars;
        otherwise no new Var is created, but type inference result is verified
        against existing _output_vars, if overwrite_output is False.

        If overwrite_output is True, then the type inference result overwrites the
        existing _output_vars
        
coremltools/converters/mil/mil/operation.py