def transform_function_signatures(self, func: Function) -> None:
        """
        This utility transform a function input / output signatures from the original_dtype to
        the target_dtype.

        For instance, in the add_fp16_cast class, this member function transforms the following
        function:

            function(%input(fp32)) {
              block0() {
                % var_1 = op_1(x=%input)
                ...
                % output(fp32) = ...
              } -> (%output)
            }

        into:

            function(%input(fp16)) {
              block0() {
                # input_cast = cast(x=input, dtype="fp32")
                % var_1 = op_1(x=%input_cast)
                ...
                % output(fp32) = ...
              } -> (%output)
            }

        and function.output_types is set to [input_types.TensorType(dtype=types.fp16)],
        in which will be used in common::update_output_dtypes to upgrade the function output dtype accordingly.

        """
        # reset input signatures
        old_func_inputs = func.inputs
        new_func_inputs = {}
        cache_vars = {}

        # cast the new input into the original dtype
        for k, v in old_func_inputs.items():
            if v.is_tensor_or_scalar_of(self.origin_dtype):
                new_input = mb.placeholder(
                    shape=v.shape,
                    dtype=string_to_builtin(self.target_dtype),
                    name=v.name,
                ).outputs[0]

                if v in func.outputs:
                    new_outputs = []
                    for val in func.outputs:
                        new_outputs.append(new_input if val == v else val)
                    func.set_outputs(new_outputs)

                new_func_inputs[k] = new_input
                cast_input = mb.cast(
                    x=new_input,
                    dtype=self.origin_dtype,
                    before_op=func.operations[0] if len(func.operations) > 0 else None,
                )
                cache_vars[k] = cast_input
            else:
                new_func_inputs[k] = v
                cache_vars[k] = v

        # replace the use of the old input vars with the new cast var
        for k, v in old_func_inputs.items():
            func.replace_uses_of_var_after_op(
                anchor_op=None,
                old_var=v,
                new_var=cache_vars[k],
            )
        func._input_dict = new_func_inputs

        # reset output signatures
        if func.output_types is None:
            output_types = [input_types.TensorType(dtype=v.dtype) for v in func.outputs]
        else:
            output_types = func.output_types

        for idx, v in enumerate(output_types):
            if v.dtype == string_to_builtin(self.origin_dtype):
                output_types[idx] = input_types.TensorType(
                    dtype=string_to_builtin(self.target_dtype)
                )

        func.output_types = output_types

        This utility transform a function input / output signatures from the original_dtype to
        the target_dtype.

        For instance, in the add_fp16_cast class, this member function transforms the following
        function:

            function(%input(fp32)) {
              block0() {
                % var_1 = op_1(x=%input)
                ...
                % output(fp32) = ...
              } -> (%output)
            }

        into:

            function(%input(fp16)) {
              block0() {
                # input_cast = cast(x=input, dtype="fp32")
                % var_1 = op_1(x=%input_cast)
                ...
                % output(fp32) = ...
              } -> (%output)
            }

        and function.output_types is set to [input_types.TensorType(dtype=types.fp16)],
        in which will be used in common::update_output_dtypes to upgrade the function output dtype accordingly.

        
coremltools/converters/mil/mil/passes/defs/quantization.py