Skip to content

Type Inference

Here is a step-by-step guide on how to deal with type inference.

Summary of Steps

For type inference, there are 3 main things to check -- Target typing, argument typing and the type parameter bound declared.

Steps

The steps will be clearer with an actual example but here are the main points

1
2
public <T extends boundType> T foo(paramType par) {}
varType var = foo(new ArgType());
  • Target typing:
    • Check the return type of the method, T
    • Check type of variable assigned to, varType. If none, move on to other steps.
    • Here, T <: varType
  • Argument typing:
    • Check the parameter type of the method, usually <? extends T> / <? super T>
    • Check the type of argument passed in, argType
    • If paramType is <? extends T>, argType <: T
    • If paramType is <? super T>, T <: argType
  • Type parameter bound:
    • Check the declared type for the generic type (between <>)
    • Here, T <: boundType

After getting all the constraints, compare the bounds and choose the most specific bound. If one cannot be determined e.g. T <: ColoredCircle & Circle <: T, we get an error.

Subtypes

Remember the compiler does not consider any types not found from the above analysis. If you have Circle <: T <: getAreable, T is inferred as Circle and not ColoredCircle.

The idea behind this is Circle can have many subclasses -- TransparentCircle, SpinningCircle etc., the compiler won't be able to determine which it is or even be aware of all the subtypes.

Step-by-step

Example 1

We will look at the following example from the notes where a type is successfully inferred.

1
2
public <T extends Circle> T bar(Seq<? super T> seq)
GetAreable c = bar(new Seq<Circle>());
Target type Argument type Type parameter

Target type: From the code snippet, return type of the method is T and type of the variable assigned to is GetAreable

  • Thus, we get T <: GetAreable
Target type Argument type Type parameter
T <: GetAreable

Argument type: From the code snippet, type of the parameter is Seq<? super T> and type of argument passed in is Seq<Circle>

  • Based on subtyping, Seq<Circle> <: Seq<? super T>
  • Thus, we get T <: Circle
Deriving T <: Circle
  1. <? super T> is a lower-bounded wildcard bounded by T
  2. We are given Seq<Circle> <: Seq<? super T>
  3. For 2 to hold, Circle <: ? super T
  4. We need Circle to be a supertype of T
  5. Thus, T <: Circle
Target type Argument type Type parameter
T <: GetAreable T <: Circle

Type parameter bound: From the code snipppet, we can see the type declaration <T extends Circle>

  • Thus, we get T <: Circle
Target type Argument type Type parameter
T <: GetAreable T <: Circle T <: Circle

Given the above bounds, we can determine the most specific bound is T <: Circle (since Circle <: GetAreable), thus T is inferred as Circle.

Example 2

Here is another example

1
2
public <T extends Circle> T foo(Seq<? extends T> seq)
foo(new Seq<ColoredCircle>());
Target type Argument type Type parameter

Target type: From code snippet, there is no variable assigned to, so we move on

Target type Argument type Type parameter
-

Argument type: From code snippet, type of the parameter is Seq<? extends T> and type of argument passed in is Seq<ColoredCircle>

  • Based on subtyping, Seq<ColoredCircle> <: Seq<? extends T>
  • Thus, we get ColoredCircle <: T
Deriving ColoredCircle <: T
  1. <? extends T> is a upper-bounded wildcard bounded by T
  2. We are given Seq<ColoredCircle> <: Seq<? extends T>
  3. For 2 to hold, ColoredCircle <: ? extends T
  4. We need ColoredCircle to be a subtype of T
  5. Thus, ColoredCircle <: T
Target type Argument type Type parameter
- ColoredCircle <: T

Type parameter bound: From the code snipppet, we can see the type declaration <T extends Circle>

  • Thus, we get T <: Circle
Target type Argument type Type parameter
- ColoredCircle <: T T <: Circle

Given the above bounds, we get ColoredCircle <: T <: Circle so the most specific bound is ColoredCircle <: T. Thus, T is inferred as ColoredCircle.

Failure

This is an example from the notes where the bound cannot be determined.

1
2
public <T extends Circle> T qux(Seq<? extends T> seq)
ColoredCircle c = qux(new Seq<GetAreable>());
Target type Argument type Type parameter

Target type: From the code snippet, return type of method is T and type of the variable assigned to is ColoredCircle

  • Thus, we get T <: ColoredCircle
Target type Argument type Type parameter
T <: ColoredCircle

Argument type: From code snippet, type of the parameter is Seq<? extends T> and type of argument passed in is Seq<GetAreable>

  • Based on subtyping, Seq<GetAreable> <: Seq<? extends T>
  • Thus, we get GetAreable <: T
Deriving GetAreable <: T
  1. <? extends T> is a upper-bounded wildcard bounded by T
  2. We are given Seq<GetAreable> <: Seq<? extends T>
  3. For 2 to hold, GetAreable <: ? extends T
  4. We need GetAreable to be a subtype of T
  5. Thus, GetAreable <: T
Target type Argument type Type parameter
T <: ColoredCircle GetAreable <: T

Type parameter bound: From the code snipppet, we can see the type declaration <T extends Circle>

  • Thus, we get T <: Circle
Target type Argument type Type parameter
T <: ColoredCircle GetAreable <: T T <: Circle

Given the above bounds, we get T <: ColoredCircle (and also <: Circle) and GetAreable <: T.

However, since ColoredCircle <: Circle <: GetAreable, T cannot be both a subtype of Circle and supertype of GetAreable. Thus, the compiler cannot infer a bound and an error is thrown.