116 lines
4.6 KiB
OCaml
116 lines
4.6 KiB
OCaml
(** Resolved types: what [Ast.texpr] means once names are looked up.
|
|
|
|
The AST's type expressions are surface syntax — [Tname "Ptr"] and
|
|
[Tapp ("Option", ...)] are just names there. Here they are the real thing,
|
|
and two types are the same type exactly when they are structurally equal.
|
|
|
|
Milestone 2 has no generics, so there is no unification and no substitution:
|
|
a type variable is parsed, carried, and rejected the moment a value would
|
|
have to have it. That rejection lives in [Check]; this module only names
|
|
the shape. *)
|
|
|
|
(* Machine integer types. Signedness and width are both part of the type —
|
|
there is no implicit widening anywhere, per plan.org. *)
|
|
type ikind = I8 | I16 | I32 | I64 | U8 | U16 | U32 | U64
|
|
|
|
type fkind = F32 | F64
|
|
|
|
type t =
|
|
| Int of ikind
|
|
| Float of fkind
|
|
| Bool
|
|
| String
|
|
| Unit (* the zero-sized type, not C's void *)
|
|
| Never (* return, exit, error: no value at all *)
|
|
| Named of string (* a struct or union declared in the file *)
|
|
(* A C enum: an i32 at run time, but its own type, so a keyword at a call
|
|
site has something to resolve against and a plain integer does not fit. *)
|
|
| Enum of string
|
|
| Slice of t (* [T] ptr+len, non-owning *)
|
|
| Array of int64 * t (* [n T] inline, a value, copies *)
|
|
| Map of t * t (* {K V} *)
|
|
| Ptr of t (* (Ptr T) *)
|
|
| Option of t (* (Option T) *)
|
|
| Fn of t list * t (* (Fn [T ...] R) *)
|
|
| Var of string (* a type variable — milestone 5 *)
|
|
|
|
let signed = function
|
|
| I8 | I16 | I32 | I64 -> true
|
|
| U8 | U16 | U32 | U64 -> false
|
|
|
|
let bits = function
|
|
| I8 | U8 -> 8 | I16 | U16 -> 16 | I32 | U32 -> 32 | I64 | U64 -> 64
|
|
|
|
let bits_f = function F32 -> 32 | F64 -> 64
|
|
|
|
let ikind_of_name = function
|
|
| "i8" -> Some I8 | "i16" -> Some I16 | "i32" -> Some I32 | "i64" -> Some I64
|
|
| "u8" -> Some U8 | "u16" -> Some U16 | "u32" -> Some U32 | "u64" -> Some U64
|
|
| _ -> None
|
|
|
|
let fkind_of_name = function
|
|
| "f32" -> Some F32 | "f64" -> Some F64 | _ -> None
|
|
|
|
(* Every name the resolver accepts as a primitive type. The list exists so a
|
|
near-miss can be reported as the typo it is. *)
|
|
let primitive_names =
|
|
[ "i8"; "i16"; "i32"; "i64"; "u8"; "u16"; "u32"; "u64";
|
|
"f32"; "f64"; "bool"; "string"; "Unit"; "Never" ]
|
|
|
|
let ikind_name k =
|
|
(if signed k then "i" else "u") ^ string_of_int (bits k)
|
|
|
|
let fkind_name = function F32 -> "f32" | F64 -> "f64"
|
|
|
|
(* Structural equality is the whole story: no subtyping, no coercion between
|
|
machine types, no variance. Written out rather than using [=] so that adding
|
|
a case with a function or a mutable field cannot silently break it. *)
|
|
let rec equal a b =
|
|
match a, b with
|
|
| Int x, Int y -> x = y
|
|
| Float x, Float y -> x = y
|
|
| Bool, Bool | String, String | Unit, Unit | Never, Never -> true
|
|
| Named x, Named y | Enum x, Enum y -> String.equal x y
|
|
| Slice x, Slice y -> equal x y
|
|
| Array (n, x), Array (m, y) -> Int64.equal n m && equal x y
|
|
| Map (k, v), Map (k', v') -> equal k k' && equal v v'
|
|
| Ptr x, Ptr y -> equal x y
|
|
| Option x, Option y -> equal x y
|
|
| Fn (ps, r), Fn (ps', r') ->
|
|
List.length ps = List.length ps'
|
|
&& List.for_all2 equal ps ps'
|
|
&& equal r r'
|
|
| Var x, Var y -> String.equal x y
|
|
| _ -> false
|
|
|
|
let rec to_string = function
|
|
| Int k -> ikind_name k
|
|
| Float k -> fkind_name k
|
|
| Bool -> "bool"
|
|
| String -> "string"
|
|
| Unit -> "Unit"
|
|
| Never -> "Never"
|
|
| Named n | Enum n -> n
|
|
| Slice t -> "[" ^ to_string t ^ "]"
|
|
| Array (n, t) -> Printf.sprintf "[%Ld %s]" n (to_string t)
|
|
| Map (k, v) -> Printf.sprintf "{%s %s}" (to_string k) (to_string v)
|
|
| Ptr t -> "(Ptr " ^ to_string t ^ ")"
|
|
| Option t -> "(Option " ^ to_string t ^ ")"
|
|
| Fn (ps, r) ->
|
|
Printf.sprintf "(Fn [%s] %s)"
|
|
(String.concat " " (List.map to_string ps)) (to_string r)
|
|
| Var n -> n
|
|
|
|
let is_numeric = function Int _ | Float _ -> true | _ -> false
|
|
|
|
(* Ordering and equality are defined on machine types and on nothing else at
|
|
milestone 2 — strings, structs and slices have no built-in [=], because an
|
|
unconstrained type supports only what every type supports (plan.org, Types). *)
|
|
let is_comparable = function Enum _ -> true | t -> is_numeric t
|
|
|
|
(* [Never] is the type of an expression that does not produce a value: return,
|
|
an early-returning `some`, exit. It fits anywhere, and that is the only
|
|
place anything resembling subtyping exists. *)
|
|
let fits ~expected ~actual =
|
|
match actual with Never -> true | _ -> equal expected actual
|