///|
/// The types of PurePy, and the annotations that spell them.
///
/// A port of the reference's `type_syntax.py`. Two layers, as there: a
/// `TypeExpr` is what an annotation SAYS, with class names unresolved, and a
/// `Type` is what it MEANS once every name has been looked up in the module's
/// context. The checker resolves one into the other (`resolve_type` in
/// `lib/check`), because only it has the context.
pub(all) enum Primitive {
Object
Never
NoneType
Bool
Int
Float
Str
Sized
} derive(Eq, Compare, Debug)
///|
/// The name an annotation spells a primitive with, and the one `render`
/// prints.
pub fn Primitive::name(self : Primitive) -> String {
match self {
Object => "object"
Never => "Never"
NoneType => "None"
Bool => "bool"
Int => "int"
Float => "float"
Str => "str"
Sized => "Sized"
}
}
///|
/// Every primitive, in the reference's declaration order.
pub let primitives : Array[Primitive] = [
Object,
Never,
NoneType,
Bool,
Int,
Float,
Str,
Sized,
]
///|
/// The value of a literal type.
///
/// Equality compares the value's Python type as well as the value, as
/// `LiteralType.__eq__` does, since `True == 1` and `1 == 1.0` hold between
/// values of different types and `Literal[True]` is not `Literal[1]`. Separate
/// arms give that for free.
pub(all) enum Lit {
NoneLit
Bool(Bool)
Int(BigInt)
Float(Double)
Str(String)
} derive(Eq, Compare, Debug)
///|
/// Python's `repr` of the value, which is what `Literal[...]` prints.
pub fn Lit::repr(self : Lit) -> String {
match self {
NoneLit => "None"
Bool(true) => "True"
Bool(false) => "False"
Int(n) => n.to_string()
Float(d) => @basic.py_float_repr(d)
Str(s) => @basic.py_repr(s)
}
}
///|
/// What an annotation says, before its class names are resolved.
pub(all) enum TypeExpr {
PrimE(Primitive)
ListE(TypeExpr)
TupleE(Array[TypeExpr])
DictE(TypeExpr)
CallableE(Array[TypeExpr], TypeExpr)
LitE(Lit)
/// A name, possibly applied to type arguments: a class, a type parameter
/// or a type alias, which only the context can tell apart.
TypeName(Array[String], Array[TypeExpr])
UnionE(TypeExpr, TypeExpr)
} derive(Eq, Debug)
///|
/// A type. A class is named by its qualified name, which is what identifies
/// it in the class table: two classes of one name in two modules are two
/// classes.
pub(all) enum Type {
Prim(Primitive)
List(Type)
Tuple(Array[Type])
Dict(Type)
Callable(Array[Type], Type)
Literal(Lit)
/// A type parameter, by name.
TypeVar(String)
/// A class, by its qualified name in the class table, applied to its type
/// arguments.
Class(String, Array[Type])
Union(Type, Type)
/// A function whose last `n` parameters have defaults, under a profile
/// with `DefaultArguments` (#56): callable with any count from the
/// required ones to all of them. Never produced under `core`.
Defaulted(Array[Type], Int32, Type)
/// A builtin a profile adds -- `sorted`, `abs`, `zip` -- whose calls are
/// typed by a rule rather than by a signature (`lib/check`'s
/// `builtin_call_type`). Never produced under `core`.
Builtin(String)
} derive(Eq, Compare, Debug)
///|
pub let t_object : Type = Prim(Object)
///|
pub let t_never : Type = Prim(Never)
///|
pub let t_none : Type = Prim(NoneType)
///|
pub let t_bool : Type = Prim(Bool)
///|
pub let t_int : Type = Prim(Int)
///|
pub let t_float : Type = Prim(Float)
///|
pub let t_str : Type = Prim(Str)
///|
pub let t_sized : Type = Prim(Sized)
///|
/// The type variables a type mentions.
pub fn fv(tau : Type) -> Array[String] {
let out : Array[String] = []
fn go(t : Type) -> Unit {
match t {
TypeVar(a) => if !out.contains(a) { out.push(a) }
List(s) | Dict(s) => go(s)
Tuple(ss) | Class(_, ss) =>
for s in ss {
go(s)
}
Callable(ps, r) | Defaulted(ps, _, r) => {
go(r)
for p in ps {
go(p)
}
}
Union(a, b) => {
go(a)
go(b)
}
_ => ()
}
}
go(tau)
out
}
///|
/// A type as a message prints it.
pub fn render(tau : Type) -> String {
match tau {
Prim(p) => p.name()
List(s) => "list[\{render(s)}]"
Tuple(ts) => "tuple[" + ts.map(render).join(", ") + "]"
Dict(s) => "dict[str, \{render(s)}]"
Callable(ps, r) =>
"Callable[[" + ps.map(render).join(", ") + "], \{render(r)}]"
Literal(v) => "Literal[\{v.repr()}]"
TypeVar(a) => a
Class(c, []) => c
Class(c, ts) => c + "[" + ts.map(render).join(", ") + "]"
Union(s, t) => "\{render(s)} | \{render(t)}"
Defaulted(ps, n, r) => {
let shown = ps.mapi(fn(i, p) {
if i >= ps.length() - n {
render(p) + "="
} else {
render(p)
}
})
"Callable[[" + shown.join(", ") + "], \{render(r)}]"
}
Builtin(name) => "builtin \{name}"
}
}
///|
/// The type a literal's value belongs to; any other type is its own base.
pub fn base_type(tau : Type) -> Type {
match tau {
Literal(v) => lit_base(v)
_ => tau
}
}
///|
pub fn lit_base(v : Lit) -> Type {
match v {
NoneLit => t_none
Bool(_) => t_bool
Int(_) => t_int
Float(_) => t_float
Str(_) => t_str
}
}
///|
/// The literal a constant is, if it is one a literal type can hold.
pub fn lit_of_constant(c : @ast.Constant) -> Lit? {
match c {
None => Some(NoneLit)
Bool(b) => Some(Bool(b))
Int(n) => Some(Int(n))
Float(d) => Some(Float(d))
Str(s) => Some(Str(s))
_ => None
}
}
// ---------------------------------------------------------------------------
// Qualified names
///|
/// `a`, `a.b`, `a.b.c` as its parts; `None` for anything else.
pub fn dotted_name(e : @ast.Expr) -> Array[String]? {
match e {
Name(id~, ..) => Some([id])
Attribute(value~, attr~, ..) =>
match dotted_name(value) {
Some(q) => Some([..q, attr])
None => None
}
_ => None
}
}
// ---------------------------------------------------------------------------
// Annotations
///|
/// What an annotation says, or `None` when it is not one PurePy has.
pub fn parse_annotation(e : @ast.Expr) -> TypeExpr? {
match e {
Constant(value=None, ..) => Some(PrimE(NoneType))
Name(id~, ..) => {
for p in primitives {
if p.name() == id {
return Some(PrimE(p))
}
}
Some(TypeName([id], []))
}
Attribute(..) =>
match dotted_name(e) {
Some(q) => Some(TypeName(q, []))
None => None
}
BinOp(op=BitOr, left~, right~, ..) =>
match (parse_annotation(left), parse_annotation(right)) {
(Some(a), Some(b)) => Some(UnionE(a, b))
_ => None
}
Subscript(value~, slice~, ..) => parse_subscript(value, slice)
_ => None
}
}
///|
fn parse_subscript(value : @ast.Expr, slice : @ast.Expr) -> TypeExpr? {
let head = match value {
Name(id~, ..) => Some(id)
_ => None
}
if head == Some("Literal") {
let indices = subscript_args(slice)
return match (indices, literal_type(slice)) {
([_], Some(Float(_))) => None
([_], Some(v)) => Some(LitE(v))
_ => None
}
}
if head == Some("Callable") {
return parse_callable(subscript_args(slice))
}
guard parse_annotations(subscript_args(slice)) is Some(args) else {
return None
}
match head {
Some("list") => if args.length() == 1 { Some(ListE(args[0])) } else { None }
Some("tuple") => Some(TupleE(args))
Some("dict") =>
if args.length() == 2 && args[0] == PrimE(Str) {
Some(DictE(args[1]))
} else {
None
}
_ =>
match dotted_name(value) {
Some(q) => Some(TypeName(q, args))
None => None
}
}
}
///|
/// The literal an expression spells: a constant, or a negated number.
pub fn literal_type(e : @ast.Expr) -> Lit? {
match e {
Constant(value~, ..) => lit_of_constant(value)
UnaryOp(op=USub, operand=Constant(value~, ..), ..) =>
match value {
Int(n) => Some(Int(-n))
Float(d) => Some(Float(-d))
_ => None
}
_ => None
}
}
///|
fn parse_callable(args : Array[@ast.Expr]) -> TypeExpr? {
guard args.length() == 2 && args[0] is List(elts~, ..) else { return None }
match (parse_annotations(elts), parse_annotation(args[1])) {
(Some(ps), Some(r)) => Some(CallableE(ps, r))
_ => None
}
}
///|
pub fn subscript_args(s : @ast.Expr) -> Array[@ast.Expr] {
match s {
Tuple(elts~, ..) => elts
_ => [s]
}
}
///|
fn parse_annotations(es : Array[@ast.Expr]) -> Array[TypeExpr]? {
let out = []
for e in es {
match parse_annotation(e) {
Some(t) => out.push(t)
None => return None
}
}
Some(out)
}
///|
/// The types `taus` put for the parameters `alphas` in `sigma`.
pub fn substitute(
taus : ArrayView[Type],
alphas : ArrayView[String],
sigma : Type,
) -> Type {
match sigma {
TypeVar(a) =>
match alphas.search(a) {
Some(i) => taus[i]
None => sigma
}
List(s) => List(substitute(taus, alphas, s))
Tuple(ss) => Tuple(ss.map(fn(c) { substitute(taus, alphas, c) }))
Dict(s) => Dict(substitute(taus, alphas, s))
Callable(ps, r) =>
Callable(
ps.map(fn(p) { substitute(taus, alphas, p) }),
substitute(taus, alphas, r),
)
Defaulted(ps, n, r) =>
Defaulted(
ps.map(fn(p) { substitute(taus, alphas, p) }),
n,
substitute(taus, alphas, r),
)
Class(c, ss) => Class(c, ss.map(fn(a) { substitute(taus, alphas, a) }))
Union(s, t) =>
Union(substitute(taus, alphas, s), substitute(taus, alphas, t))
_ => sigma
}
}
///|
/// A class's fields, generic in its type parameters.
pub(all) struct FieldScheme {
type_params : Array[String]
fields : Array[(String, Type)]
} derive(Eq, Debug)
///|
/// The fields at the type arguments `taus`.
pub fn instantiate(
pi : FieldScheme,
taus : Array[Type],
) -> Array[(String, Type)] {
pi.fields.map(fn(f) { (f.0, substitute(taus[:], pi.type_params[:], f.1)) })
}