Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Updates for the latest implementation

...

Number:

GEP-8

Title:

Static Type Checking

Version:

78

Type:

Feature

Status:

Draft

Leader:

Cédric Champeau

Created:

2011-10-08

Last modification:

20112012-1102-0921

Abstract: Static Type Checking

...

Implementation details

Development branch

The code is currently in heavy development, so make sure that you checkout the code from Git. The code is in Since Groovy 2.0-beta-2, code has been merged into master branch. However, if heavy developments are done on the type checker, it is advisable to work on the grumpy branch. It adds an AST transformation named TypeChecked. If set, then the AST transformation will perform type inference and store type information in AST nodes metadata. Eventually, if errors are found, it will add errors to the compiler through a dedicated addStaticTypeError method which basically does the same as the traditional addError method but prefixes the messages with a "Static type checking" message. This is done to help the developer determine whether the error he is seeing is a "plain Groovy" error, or an error thrown by the STC mode.

...

Feature

Example

Behaviour

Status

Method does not exist

Code Block
def method() {
...
}
methode() // typo

Complains about undefined method

Implemented

Property does not exist

Code Block
class A {
   int x
}
A obj = new A()
a.y = 2

Complains about undefined property "y"

Implemented

Assignment type checking

Code Block
int x=2
x = 'String'

Assigning a String to an int is forbidden

Implemented

Incompatible binary expressions

Code Block
1+'string'+1

Checks that arguments of a binary expression are compatible (here, no 'plus' method is available

Implemented

Possible loss of precision (1/2)

Code Block
long myLong = ...
int myInt = myLong

Complains about possible loss of precision

Implemented

Possible loss of precision (2/2)

Code Block
int myInt = 2L

Will not complain because '2' can be represented as an int

Implemented

Arrays components

Code Block
String[] arr = { '1','2','3' }
arr[2] = 200

Cannot assign an int value in an array of type String[]

Implemented

Method return type check

Code Block
String method() { 'Hello' }
int x = method() // return types don't match

Ensures that assignments are compatible with method return type

Implemented

Explicit return type checking

Code Block
int method() {
   return 'String' // return type is not compatible
}

Ensures that returned value is compatible with declared return type

Implemented

Implicit return type checking

Code Block
int method() {
   'String' // return type is not compatible
}

Ensures that returned value is compatible with declared return type

Implemented

Implicit toString()

Code Block
String method(String name) {
   StringBuilder sb = new StringBuilder()
   sb 'Hi ' << name << '!'
}

Implicit call to toString()

Implemented

Basic type inference

Code Block
def str = 'My string'
str.toUpperCase() // type of 'str' is inferred

Method calls as well as property access are checked against inferred type

Implemented

Basic flow analysis

Code Block
def o = new Object()
o = 1
o = 'Hello'
o.toUpperCase()

Last method call will not complain because type of 'o' at this point can be inferred

Implemented

Instance of

Code Block
def o
...
if (o instanceof String) {
   o.toUpperCase() // no explicit cast required
}

Casts should not be necessary when type can be inferred from a previous instanceof check

Implemented

DefaultGroovyMethods support

Code Block
'123'.toInteger() // toInteger() is a Groovy extension method

Method calls can be resolved against Groovy extension methods

In progress (no type inference for closure arguments)Implemented

with

Code Block
class A {
   int x
}
def a = new A()
a.with {
   x = 1
}

Static type checking should be aware of the "with" structure

In progressImplemented

Categories

Code Block
use (MyStringCategory) {
   'string'.methodInStringCategory()
}

Compiler should be aware that extension method is found in a category

N/A (support will be limited as category support is inherently dynamic)

Groovy list constructor

Code Block
Dimension d = [100,200]

Type checks the arguments and the number of arguments

Implemented

Groovy map constructor

Code Block
Bean myBean = [x:100,y:200]

Type checks the properties and checks for wrong property names

Implemented

Closure parameter types

Code Block
def closure = { int x, int y -> x+y }
closure(1,2)
closure('1','2') // complains

Type checking the arguments when calling a closure

Implemented

Closure return type inference

Code Block
def closure = { int x, int y -> x+y }
int sum = closure(1,2)

Closure return type can be inferred from block

Implemented

Method return type inference

Code Block
def method(int x, int y) { x+y }
int sum = method(1,2)

Return type can be inferred from a method if the method is itself annotated with @TypeChecked (or class is annotated with @TypeChecked)

Implemented

Multiple assignments

Code Block
def (x,y) = [1,2]

In case of inline declaration, type check arguments.

Implemented

Multiple assignments from a variable

Code Block
def (x,y) = list

In case of inline declaration, type check arguments.

Unsupported

Generics

Code Block
List<String> list = []
List<String> list = ['a','b','c']
List<String> list = [1,2,3] // should throw error

Type checking of generic parameters

Mostly implemented (some edge cases may fail)Implemented

Spread operator

Code Block
def list = ['a','b','c']
list*.toUpperCase()

Type checking against component type

Implemented

Closure shared variables
Code Block
titleClosure shared variables
languagegroovy
def x = new Date()
def cl = { x = 'hello' }
cl()
x.toUpperCase() // should throw an error because the toUpperCase() method doesn't belong to both Date and String classes
Type check assignments of closure shared variables. The type checker is required to perform a two-pass verification, in order to check that method calls on a closure shared variables belong to the lowest upper bound of all assignment types.Implemented

Open discussions

Closure parameter type inference

...

Are not properly recognized. You have to explicitly set the type of the "it" parameter inside the closure. It is because the expected parameter types of closures are unknown at compile time. There is a discussion about how to add this type information to source code so that the inference engine can deal with them properly. The implementation of closure parameter type inference requires a change to the method signatures. It will probably not belong to the initial release of the type checker.

Unification Types

In cases of for example "x instanceof A || x instanceof B" with A and B being unrelated we could still make an artificial union kind of type, that contains everthing present in A and B, to allow those kinds of method calls. The alternative to this is to allow only methods from Object here, which is less interesintg. This typing can also be used for multicatch, ensuring that a method call is only valid if it exists on each of the exceptions for the multicatch. In the current implementation (Okt-14-2011) the multicatch is already expanded at the point @TypeChecked will check. Meaning effectively this already represents a kind of union type, as the same code is in each catch block and thus the method call would fail, if the method is not available on each type. The proposed behaviour is therefore to align the instanceof case with multicatch.

...