We'd like to be able to compile to a relatively low level. To this end, we need to have predetermined the types of the values used in the program code.
We'll use 2 different concepts of types. One is parsed types. There are the ones the programmer writes explicitly. These we'll callexplicit types or `EType`. The other is calcaluted types, ie. result of type checking. These types we'll call value types or `VType`, because they tell us, within the compiler, the types of values.
For a type checker to be able to determine all the types of all values, the program is required to contain the information need. That information will be dictated by the programmer, therefore the programmer needs a way to write types explicitly. We'll do this by adding types to the language.
We currently have implemented 2 types of explicit types: identifier types and array types. Identifier types are just like identifier expressions, ie. an identifier token. An array type is a type enclosed in `[` and `]`, eg. `[int]`.
We've added the if-statement checking for a `:`-token.
### 8.1.4 Parsing functions
```ts
type StmtKind =
// ...
| {
type: "fn",
ident: string,
params: Param[],
returnType?: EType,
body: Expr,
}
// ...
;
```
We've added the `returnType` field. It is optional, because the return type my be omitted, in which case, it will be assigned null as the return type.
```ts
class Parser {
// ...
public parseFn(): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return this.stmt({ type: "error" }, pos);
}
const ident = this.current().identValue!;
this.step();
if (!this.test("(")) {
this.report("expected '('");
return this.stmt({ type: "error" }, pos);
}
const params = this.parseFnParams();
let returnType: EType | null = null;
if (this.test("->")) {
this.step();
returnType = this.parseEType();
}
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ type: "error" }, pos);
}
const body = this.parseBlock();
if (returnType === null) {
return this.stmt({ type: "fn", ident, params, body }, pos);
}
return this.stmt({ type: "fn", ident, params, returnType, body }, pos);
}
// ...
}
```
We've added the if statement checking for `->`, and the handling of the mutable `returnType` variable.
## 8.2 Value types
The type checking process takes the program with some amount of explicit types. It then figures out the types of all values and operations. In this process, it will also check that all values and operations operate on and have themselves valid and appropriate value types. The type checker works with and produces value types or `VType`.
The error-type is for convenience of implementation, just like in the parser. The unknown-type is for type inference, when we don't yet know the type. the null-, int-, string-, bool- and struct-types are uniform in that all values of each type can be assigned to each type. There's no difference between to integers, although the values might differ, and the same for two struct values, even though they may contain a different set of fields. Array-types and function-types also contain their details, ie. inner type in arrays and params and return type in functions.
When checking the types, we need to store the resulting value types. We'll choose to store these inside the AST itself. This means we'll mutate the AST in the type checker.