Compiler Cannot Infer Return Type

Is it possible to infer type parameters from what return values are assigned to?

The current rules for type inference are explicit. How the return values are used is not taken into account:

Type inference is based on

  • a type parameter list
  • a substitution map M initialized with the known type arguments, if any
  • a (possibly empty) list of ordinary function arguments (in case of a function call only)

As of Go 1.18 might simply rewrite your function to accept an argument of the required type; this has also the benefit of not hiding allocations inside the function body:

func FromInterfaceSlice[T any](s []interface{}, dst []T) error {
if len(s) != len(dst) {
return errors.New("lengths don't match")
}
for i, v := range s {
vt, ok := v.(T)
if !ok {
return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
}
dst[i] = vt
}
return nil
}

And pass in a destination slice with the required length:

func main() {
src := []interface{}{1, 2, 3}
m := make([]int, len(src))
_ = FromInterfaceSlice(src, m)
fmt.Println(m)
}

If you can't or don't want to determine the slice's length beforehand, you are left with explicit instantiation:

var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
// ^^^ explicit type argument

Also the type parameters are still not inferrable with := shorthand declaration:

// what is m???
m, err := FromInterfaceSlice([]interface{}{1, 2, 3})

Compiler cannot infer if an object of `impl trait` has impled another trait in Rust

Ok I think I've find the ad-hoc solution for my problem: additional Wrapper:

struct Wrapper<P: A> {
p: P
}

impl<P: A> A for Wrapper<P> {...}
impl<P: A> B for Wrapper<P> {...}

fn func_to_return_impl_A() -> Wrapper<impl A> {...}

Then I can use methods of trait B:

func_to_return_impl_A().method_of_trait_B(...);

Why cant typescript infer the return type when using Differentiating Type checks

Typescript doesn't do this automatically because that would require evaluating your function, i.e. actually running the code. Even though the parameters are known at compile time and it might theoretically be possible it doesn't try anything like that. Maybe in this case it seems like it should be simple but that's not true in the general case.

When it narrows the type for you inside the block, it is saying only that if the conditional is true, then the type is X, but it's not actually evaluating the conditional with a value.

Why can't TypeScript infer the return type of this function?

The inferred types for object literals created with spread syntax are not always ideal, especially when computed properties are involved. In this case T is intersected with an index signature {[x: string]: V}, whereas Omit<T, A> & {[K in A]: V} would make more sense (the intersection of T without property A and a separate object with key A and value T).

I don't see how you would be able to solve this without an explicit return type, but if you add the return type, at least the correct types get inferred. Perhaps you can do something similar for the other versions of your function.

const cloneWith = <T, A extends keyof T, V>(
i: T,
a: A,
value: V
): Omit<T, A> & {[K in A]: V} => ({
...i,
[a]: value,
});

const source = { name: 'bob', age: 90 };
const result = cloneWith(source, 'age', 'old!');
type TestSource = typeof source.age // number
type TestResult = typeof result.age // string

TypeScript playground

Generic methods in .NET cannot have their return types inferred. Why?

The general principle here is that type information flows only "one way", from the inside to the outside of an expression. The example you give is extremely simple. Suppose we wanted to have type information flow "both ways" when doing type inference on a method R G<A, R>(A a), and consider some of the crazy scenarios that creates:

N(G(5))

Suppose there are ten different overloads of N, each with a different argument type. Should we make ten different inferences for R? If we did, should we somehow pick the "best" one?

double x = b ? G(5) : 123;

What should the return type of G be inferred to be? Int, because the other half of the conditional expression is int? Or double, because ultimately this thing is going to be assigned to double? Now perhaps you begin to see how this goes; if you're going to say that you reason from outside to inside, how far out do you go? There could be many steps along the way. See what happens when we start to combine these:

N(b ? G(5) : 123)

Now what do we do? We have ten overloads of N to choose from. Do we say that R is int? It could be int or any type that int is implicitly convertible to. But of those types, which ones are implicitly convertible to an argument type of N? Do we write ourselves a little prolog program and ask the prolog engine to solve what are all the possible return types that R could be in order to satisfy each of the possible overloads on N, and then somehow pick the best one?

(I'm not kidding; there are languages that essentially do write a little prolog program and then use a logic engine to work out what the types of everything are. F# for example, does way more complex type inference than C# does. Haskell's type system is actually Turing Complete; you can encode arbitrarily complex problems in the type system and ask the compiler to solve them. As we'll see later, the same is true of overload resolution in C# - you cannot encode the Halting Problem in the C# type system like you can in Haskell but you can encode NP-HARD problems into overload resolution problems.) (See below)

This is still a very simple expression. Suppose you had something like

N(N(b ? G(5) * G("hello") : 123));

Now we have to solve this problem multiple times for G, and possibly for N as well, and we have to solve them in combination. We have five overload resolution problems to solve and all of them, to be fair, should be considering both their arguments and their context type. If there are ten possibilities for N then there are potentially a hundred possibilities to consider for N(N(...)) and a thousand for N(N(N(...))) and very quickly you would have us solving problems that easily had billions of possible combinations and made the compiler very slow.

This is why we have the rule that type information only flows one way. It prevents these sorts of chicken and egg problems, where you are trying to both determine the outer type from the inner type, and determine the inner type from the outer type and cause a combinatorial explosion of possibilities.

Notice that type information does flow both ways for lambdas! If you say N(x=>x.Length) then sure enough, we consider all the possible overloads of N that have function or expression types in their arguments and try out all the possible types for x. And sure enough, there are situations in which you can easily make the compiler try out billions of possible combinations to find the unique combination that works. The type inference rules that make it possible to do that for generic methods are exceedingly complex and make even Jon Skeet nervous. This feature makes overload resolution NP-HARD.

Getting type information to flow both ways for lambdas so that generic overload resolution works correctly and efficiently took me about a year. It is such a complex feature that we only wanted to take it on if we absolutely positively would have an amazing return on that investment. Making LINQ work was worth it. But there is no corresponding feature like LINQ that justifies the immense expense of making this work in general.


UPDATE: It turns out that you can encode arbitrarily difficult problems in the C# type system. C# has nominal generic subtyping with generic contravariance, and it has been shown that you can build a Turing Machine out of generic type definitions and force the compiler to execute the machine, possibly going into infinite loops. At the time I wrote this answer the undecidability of such type systems was an open question. See https://stackoverflow.com/a/23968075/88656 for details.



Related Topics



Leave a reply



Submit