@PublicEvolving
    public static <IN, OUT> TypeInformation<OUT> getMapReturnTypes(
            MapFunction<IN, OUT> mapInterface, TypeInformation<IN> inType) {
        return getMapReturnTypes(mapInterface, inType, null, false);
    }
@Test
    void testFunctionInputInOutputMultipleTimes2() {
        RichMapFunction<Tuple2<Float, Float>, ?> function =
                new FieldDuplicator<Tuple2<Float, Float>>();

        TypeInformation<?> ti =
                TypeExtractor.getMapReturnTypes(
                        function,
                        new TupleTypeInfo<Tuple2<Float, Float>>(
                                BasicTypeInfo.FLOAT_TYPE_INFO, BasicTypeInfo.FLOAT_TYPE_INFO));

        // should be
        // Tuple2<Tuple2<Float, Float>, Tuple2<Float, Float>>

        assertThat(ti.isTupleType()).isTrue();
        assertThat(ti.getArity()).isEqualTo(2);
        TupleTypeInfo<?> tti = (TupleTypeInfo<?>) ti;

        // 2nd nested level
        assertThat(tti.getTypeAt(0).isTupleType()).isTrue();
        TupleTypeInfo<?> tti2 = (TupleTypeInfo<?>) tti.getTypeAt(0);
        assertThat(tti2.getTypeAt(0)).isEqualTo(BasicTypeInfo.FLOAT_TYPE_INFO);
        assertThat(tti2.getTypeAt(1)).isEqualTo(BasicTypeInfo.FLOAT_TYPE_INFO);
        assertThat(tti.getTypeAt(0).isTupleType()).isTrue();
        TupleTypeInfo<?> tti3 = (TupleTypeInfo<?>) tti.getTypeAt(1);
        assertThat(tti3.getTypeAt(0)).isEqualTo(BasicTypeInfo.FLOAT_TYPE_INFO);
        assertThat(tti3.getTypeAt(1)).isEqualTo(BasicTypeInfo.FLOAT_TYPE_INFO);
    }