×

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

Dart Symbols

Symbols data type in Dart A symbol is an object that is the representation of an operator or identifier in Dart. These are compile-time constants.  They are used in APIs that refer...

1 minute read.

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.

Assert in DART

In any programming language, resolving error is the most unwanted and tedious task. At times it becomes very difficult to find the error in the large code. Dart offers an...

3 minutes read.

Dart Booleans

Booleans Dart has a data type named ‘bool’ to represent Boolean values : true and false, which are compile-time constants. ‘True’ or ‘False’ cannot be assigned with values 1 or 0. Example, bool...

1 minute read.

Dart Packages

Every programming language has some in-built functions stored inside the header files that makes the task much easier for the programmer. The Dart Packages refer to the compilation of a...

4 minutes read.

Dart Object-Oriented Concepts

Dart is a programming language that supports object-oriented programming. It is focused on the objects that are real-world entities and supports all the concepts of OOPS, such as objects, classes,...

4 minutes read.

Dart HTML DOM

All web pages can be viewed as objects and exist within the browser window. We can access a web page using a web browser and it needs to be connected...

3 minutes read.

Dart Numbers

Dart provides an in-built data type ‘Numbers’ that provides two types of values: intdouble 1. int data type int data type supports integer values, and their size or values range is platform-dependent. Integers...

4 minutes read.

Dart Keywords

Keywords are the reserved words of a programming language, and they have a special meaning that comes defined by the language. These cannot be used as an identifier, and all...

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.

Dart Types of Functions

Functions are the set of statements intended to perform a specific task. They accept some input from the user, perform the specified computation and generate the output. They are very...

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 Switch Case Statement

In the case of if-else statements, we prefer not to use the if-else ladder when there are many test conditions to be evaluated. In such a situation, it is preferable...

4 minutes read.

Dart Isolates

Dart successfully supports asynchronous programming which runs our program without any hindrance. This asynchronous programming is used to achieve concurrency. Dart supports concurrent programming with features such as ‘ async-await...

9 minutes read.

Lexical Scope and Closure

Lexical Scope : Lexical scope is a very important term in any programming language. It defines the scope of the variable based on the block it is contained inside. According to...

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.

Golang vs Dart

Go is the procedural programming language. It was founded in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson at Google but was launched in 2009 as the language of...

2 minutes read.

Dart Built-in Data Types

A data type defines the type of value a variable can hold, such as integer, double, or string.  Dart supports two data types:  1. Built-in data types : These are the data...

1 minute 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 Loops

Looping refers to repeating or reusing the same lines of code repeatedly until a particular test condition evaluates to true. It is also known as iterating.  It is effectively used when...

4 minutes read.