×

Dart Type inference

The task of interpreting the data types of fields, methods, local variables and most generic type arguments is handled by the static analyser. However, when not enough information is provided to the static analyser it assumes it to be of dynamic type.

Consider the following example that explains the concept of type inference working with generics. Here, a variable ‘ variable ’ holds a map that contains a map of key and value pairs.

Map< String, dynamic > variable = { '  var1  ' : ' Type ', ' var2 ' : 10 } ;

As an alternate option, we can use ‘var’ or ‘final’ instead of ‘Map’ leaving the task of inferring the type of the compiler.

var variable = { ' var1 ' : ' Type ', ' var2 ' : 10 } ; // This works similar to Map< String, Object >

The ‘ Map ’ literal interprets its type from the type of key and value pairs specified in it. The variable then interprets its own type from the type of map literal. For example, in the above example, the map has keys that are both of type ‘ String ’ while the value pairs have different types ( String and int ). So, the ‘ Map ’ literal has the type Map< String, Object >, and so does the arguments variable.

Field and method type inference

A property or function of the class that does not have a specific data type and that overrides the property or function of the superclass, inherits the data types as same as that of the superclass method.

While, a field is not declared with some specific type or inherits a type, the data type is interpreted based on the initial value assigned to it.

Static field type inference

The data types of static fields and variables are interpreted from their initializer. An important point to remember is that the inference fails if it comes across any kind of cycle ( that is, if inferring a type for the variable depends on knowing the type of that variable ).

Local variables inference

The data types of local variables are inferred from their initializer, if any. Subsequent assignments are not taken into account that means type may be inferred very precisely. If so, you can add a type annotation.

var x = 5 ; // x is inferred as an integer

x = 5.0 ;

num y = 7 ; // Here, y can inferred as integer or double

y = 7.0 ;

Type argument inference

The data type of arguments passed to constructor calls and generic method invocations are interpreted based on a combination of information. The downward information from the context of occurrence, and upward information from the arguments to the constructor or generic method. In case the compiler does not interpret the data type correctly, it is advisable to explicitly mention the data types of the argument. 

// This is interpreted as < int >[  ]

List< int > int_list = [ ] ;

// This is interpreted as < double >[ 3.0 ]

var double_list = [ 5.4 ] ;

// This is interpreted as an Iterable< int >.

var int_iterable = listOfDouble.map( ( x ) => x.toInt( ) ) ;

In the above example, ‘ x ’ is interpreted as of ‘ double ’ data type using downward information. With the help of upward information, the return type of the closure is inferred as ‘ int ’. Dart uses this return type as upward information when inferring the map( ) method’s type argument : < int >.

Substituting types

When we override a method, we re-write it or replace it with something of one type with something of a new type with some difference. Similarly, when an argument is passed to a function, we are replacing the parameter of some declared type with some another type. This could be easily understood by an example of consumers and producers. A consumer absorbs a type, and a producer generates a type. You can replace a consumer’s type with a supertype and a producer’s type with a subtype.

Let’s look at examples of simple type assignments and assignments with generic types.

Simple type assignment

Consider the following type hierarchy:

Type inference

This is a hierarchy of animals where the supertype is ‘Animal ’ and the subtypes are ‘ Monkey ’, ‘ Cat ’ and ‘ Cow ’. ‘Cat ’ has the subtypes of ‘ Lion ’ and ‘ Tiger ’.

Consider the following simple assignment where the consumer is Cat c and Cat( ) is a producer :

Cat c = Cat( ) ;

Try to understand it this way that it’s alright to replace something that consumes a specific type like here, ‘ Cat ’ with something that consumes anything which kind of contains it like here ‘ Animal ’. Therefore, replacing ‘ Cat c ’ with  ‘ Animal c ’ is allowed, because ‘ Animal ’ is a supertype of ‘ Cat ’.

Animal c = Cat( ) ;

But replacing ‘ Cat c ’ with ‘ Lion c ’ breaks type safety, because the superclass may provide a type of Cat with different behaviours, such as Tiger :

Lion c = Cat( ) ;

In a producing position, it’s safe to replace something that produces a type ‘ Cat ’ with a more specific type ‘ Lion ’. So, the following is allowed:

Cat c = Lion( ) ;

Generic type assignment

We can follow the same rules for the generic data types as well. Consider the hierarchy of lists of animals—a List of Cat is a subtype of a List of Animal, and a supertype of a List of Lion :

List< Animal > -> List< Cat > -> List< Lion >

In the following example, Lion list can be assigned to myCats because List< Lion > is a subtype of List< Cat > :

List< Cat > myCats = < Lion > [ ] ;

What do you think can the reverse be possible? Can you assign an Animal list to a List< Cat > ?

List< Cat > myCats = < Animal >[ ] ;

This assignment doesn’t pass static analysis because it creates an implicit downcast, which is disallowed from non-dynamic types such as Animal.

Version note : The packages belonging to language versions before 2.12 ( when support for null safety was introduced ), code downcast from these non-dynamic types implicitly. We can disallow non-dynamic downcasts in a pre-2.12 project by specifying implicit-casts: false in the analysis options file.

To make this code pass static analysis, use an explicit cast, which might fail at runtime.

List< Cat > myCats = < Animal >[ ] as List< Cat > ;

Methods

Remember that the producer and consumer rules apply even when we override a method. For example consider the following piece of code :

Type inference

The class ‘ Animal ’ contains the ‘ chase ’ method as the consumer and the parent function ‘ getter ’ as the producer.

For a consumer the ‘ chase( Animal ) ’ method, we can replace the parameter type with a supertype. For a producer the parent ‘ getter( ) ’ method, we can replace the return type with a subtype.

For more information, include Use sound return types when overriding methods and Use sound parameter types when overriding methods.


Related Topics

Abstract Classes in Dart

Abstract classes in Dart are those classes that only contain abstract methods  (methods that do not contain any implementation). These classes are specifically designed for the purpose of inheritance. We...

4 minutes read.

Common Collection Methods

Most of the programming languages have arrays as a way to list items together. However, Dart has a collection of data structures similar to array. These are supported by the...

3 minutes read.

Dart Type inference

The task of interpreting the data types of fields, methods, local variables and most generic type arguments is handled by the static analyser. However, when not enough information is provided...

5 minutes read.

Dart Break and Continue

Loop statements are used to change the normal sequence of flow of the program. Dart supports two types of loop control statements: Break StatementContinue Statement Break Statement : The ‘ break ’ statement is...

4 minutes read.

Dart Iterable

Iterable is a collection of elements that are accessed sequentially. These elements are accessible using the iterator getter and stepping through the values using this getter. Let us understand the setting...

6 minutes read.

Dart Inheritance

Inheritance is a promising concept of any programming language. It is a property by which a class inherits the property of another class. Moreover, the class deriving the properties of...

5 minutes read.

Dart lists data type

The most important data type in any programming language is an ordered list of elements, an array. Dart lists resemble JavaScript array lists. Example,               var...

7 minutes read.

Dart If-else-if statement

If - else - if statement enables to check the set of test expressions that evaluate to Boolean True or False, and based on this true or false the particular...

2 minutes read.

Metadata in Dart

Metadata is often referred to as the data about the data. It is a  piece of data about a basic piece of data. In the case of a Dart program,...

2 minutes read.

Dart Optional Parameters

Dart functions can have optional parameters with default values. When we create a function, we can specify parameters that calling code can provide; but if the calling code chooses not...

2 minutes read.

Dart Generators

Synchronous Generator Dart Generator is a unique function that allows us to generate price sequences. Generators return values when needed; means that the value is generated if we try to duplicate...

5 minutes read.

Dart If-else statement

In the previous section, we studied about if statements in detail. If – block is executed when the condition evaluates to true, else if the condition evaluates to false, then...

1 minute read.

Dart Single-Page Application Architecture

In a single page application architecture, the source code for a single web page loads the entire application. The responsibility of building the user interface and requesting data from the...

2 minutes read.

Type System in Dart

Dart language is a type safety enabled language that uses static and dynamic type checking to match the value of the variable with its data type at the compile-time. This...

4 minutes read.

Dart Miscellaneous Operators

Apart from the operators we studied so far, Dart provides some more operators which are as follows : Conditional OperatorCascade Notations Conditional Operators These are the operators that help in evaluating the expression...

1 minute read.

Dart Features

Dart is a fully-featured, object-oriented modern language with its roots in the programming language ‘Smalltalk’. It has some of its eminent features similar to the languages such as Java, C#...

4 minutes read.

Dart Assignment Operator

Assignment operators are the operators that assign value to the variables. The value on the right-hand side is assigned to the variable on the left-hand side. We can also use...

4 minutes read.

Dart Exception Handling

Exception Class – An exception is any runtime error that leads to abrupt termination of the program. It helps in addressing the error programmatically. It is aimed to be caught...

4 minutes read.

Dart Recursion

Recursion is one of the most important and interesting concepts in any programming language. It can be defined as a process in which a function calls itself directly or indirectly....

5 minutes read.

main function in Dart

The main( ) function is a predefined method in Dart that is also known as the entry-point of the program. The compiler begins the execution only when it comes across...

3 minutes read.