In CA, programmers do not explicitly declare types of variables and functions. Instead, a sophisticated type inference algorithm tries to automatically deduce types based on global dataflow information. Only simple type inference is carried out when compiling without optimization(the default), and no message related to types are printed out. When optimization is enabled (with either the -O or -O2 flag), however, the type inference system performs extensive analyses and produces warning messages for type errors and imprecisions that might be caused by errors in CA programs.
The type inference system produces two types of messages: type notes and type warnings. Type notes indicate type imprecisions that may or may not cause a runtime error. For example, in the following method, the let-bound variable a could hold either the integer value 1 or a string depends on the value of the argument flag.
(method classA foo (flag)
(let ((a (if flag 1 "hello world")))
(reply (* a 1))))
However, the * method is defined on the class of integers but not strings, of which "hello world" is a member. When compiling the program with the -O flag, the type inference system produces the following message.
Note: type check on (TEMP 355) : ("integer")
for ("string")
in ("foo" "self" "requester" "b") : ("classA")
called from ("foo" (TEMP 358) "requester" (TEMP 359))
in ("initial_message" "self" "requester") : ("osystem")
A type note indicates that in the context of the enclosing method, a
variable is required to hold a value of a specific type but may hold a
value of another type. Additional information in a type note helps the
programmer to locate the source of the imprecision. More concretely,
the above type note points out that in the foo method of the class
classA (line 3 of the message), the variable (TEMP
355)
is required to hold a value of type integer (line 1
of the message). However, based on interprocedual flow analysis,
the variable may also hold a value of type string for some path of
program execution (line 2 of the message). Line 4 of the message gives
the call site of the foo method, and line 5 indicates that the
method is called within the initial_message method of osystem. In our case, there is only one call to the foo method in
initial_message, and we can easily discover the source of the
type note.
Note that for this code, a runtime error is possible depends on the value of the argument flag. If flag is always true, the program executes correctly; otherwise, the program will cause a
could not find handler + for class string.
runtime error (see [5] for a complete listing of error messages).
Type notes, however, do not always imply the possibility of runtime errors. They also can be caused by errors in user programs or from true polymorphic use of a variable. Section 7.2 shows an example of type notes caused by errors. Below, we show an example of polymorphic use of a variable.
(method classA foo (flag)
(let ((a (if flag 1 1.0))
(b (if flag 2 2.0)))
(reply (* a b))))
When compiling with optimization, the following type notes are produced.
Note: type check on (TEMP 146) : ("float")
for ("integer")
in ("*" "self" "requester" "f") : ("float")
called from ("*" (TEMP 357) "requester" (TEMP 358))
in ("foo" "self" "requester" "flag") : ("classA")
Note: type check on (TEMP 49) : ("integer")
for ("float")
in ("*" "self" "requester" "i") : ("integer")
called from ("*" (TEMP 357) "requester" (TEMP 358))
in ("foo" "self" "requester" "flag") : ("classA")
This is because Concert does not (currently) allow mixed mode arithmetic, and the type inference system does not know that when a is an integer b is always an integer and likewise for floats. However, the program always executes correctly because the types of a and b will always match at run time.
The type inference system prints out type warning messages when it failed to discover any type for a variable. Type waring messages usually indicate errors in user programs. For example, the following code uses add to add to integers instead of +.
(method osystem initial_message ()
(reply (add 1 1)))
Because the method add is not defined for the integer class, the type inference system produces a string of messages when compiling with optimization on.
Type Inference
Type Inference pass 1
Note: type check on (TEMP 353) : NIL
for ("integer")
in ("initial_message" "self" "requester") : ("osystem")
called from ("initial_message" (TEMP 492) "requester")
in ("_main" "self" "requester") : ("rootclass")
Warning: no type for (TEMP 491) in ("_main" "self" "requester") : ("rootclass")
Warning: no type for (TEMP 352) in ("initial_message" "self" "requester") : ("osystem")
Warning: no type for (TEMP 353) in ("initial_message" "self" "requester") : ("osystem")
Warning: no type for (TEMP 317) in ("write_object" "self" "requester" "o") : ("consoleclass")
Warning: no type for "o" in ("write_object" "self" "requester" "o") : ("consoleclass")
Type warnings are usually accompanied by type notes as in the above example. The NIL type in the line 1 of the above type note indicates that the type inference system cannot resolve any legal type for the variable (TEMP 353) based on its context, although (TEMP 353) may hold an integer (line 2 of the type note). Because the type inference system performs global flow analysis, a single error in the user program can propagate to several places and cause multiple warning messages to be printed, as shown by the above example. The type inference system does not track down the exact origin of each type error. However, information is provided for programmers to track down errors manually, as explained in the next section.
In interactive mode (see section 6.2.1) it is possible to print out the result of type inference on the Concurrent Aggregates program to assist in debugging the program. This can be done for either just the arguments and return values of methods or for all the subexpressions and local variables. Unfortunately, the latter information is printed with respect to an internal representation of program and will not match the Concurrent aggregates program directly.
Because of the volume of detailed information used to locate precisely type errors, you should invoke the compiler interactively within emacs. Once inside the interactive mode, you can use (precise-types) function to enable full type inference followed by a call to ca2out to compile your program. After the completion of compilation, the following functions can be used.
For example, the following shows the the use the (tdb-show-method-val-types) function to display detailed information for our most recent example (involving (add 1 1)).
Concert Compiler V2.0.1 Alpha Fri Feb 25 11:40:58 CST 1994
Bugs/comments to concert-bugs@red-herring.cs.uiuc.edu
Copyright (c) 1991, 1992, 1993, 1994
The University of Illinois Board of Trustees.
All Rights Reserved.
* (precise-types)
NIL
* (ca2out "try.ca")
Input and Attribute Evaluation
Reading: /b/ca/concert2.0.1/lib/standard-prologue.ca...
...Closing: /b/ca/concert2.0.1/lib/standard-prologue.ca
Reading: /home/chien/jplevyak/tmp/z.ca...
...Closing: /home/chien/jplevyak/tmp/z.ca
Core Code Translation
Dominator Computation
Control Dependence Computation
Static Single Assignment Conversion
Type Inference
Type Inference pass 1
Note: type check on (TEMP 353) : NIL
for ("integer")
in ("initial_message" "self" "requester") : ("osystem")
called from ("initial_message" (TEMP 492) "requester")
in ("_main" "self" "requester") : ("rootclass")
Warning: no type for (TEMP 491) in ("_main" "self" "requester") : ("rootclass")
Warning: no type for (TEMP 352) in ("initial_message" "self" "requester") : ("osystem")
Warning: no type for (TEMP 353) in ("initial_message" "self" "requester") : ("osystem")
Warning: no type for (TEMP 317) in ("write_object" "self" "requester" "o") : ("consoleclass")
Warning: no type for "o" in ("write_object" "self" "requester" "o") : ("consoleclass")
Program Transformations
Output
Compilation Complete
20 Methods Output of 152 Compiled in 17 Classes
NIL
* (tdb-show-method-types)
Method of ("rootclass") ("_main" "self" "requester")
...
many more methods
Searching for (TEMP 353) locates the following entry of initial_message.
Method of ("osystem") ("initial_message" "self" "requester")
((MOVE 1 (TEMP 353)) (MOVE 1 (TEMP 354))
(SEND "add" (TEMP 353) (FUTURE (TEMP 352)) (TEMP 354))
(SEND "reply" "requester" NIL (TEMP 352)))
Returning : NIL
"self" : ("osystem")
(TEMP 352) : NIL
The expression (MOVE 1 (TEMP 353)) shows that (TEMP 353) originated from the constant 1, and the expression
(SEND "add" (TEMP 353) (FUTURE (TEMP 352)) (TEMP 354))
shows that the add method, which is not defined, is invoked on it.
In general, locating the precise origin of type errors could involve tracing across several method bodies.