use nova_diagnostics::Span;
use nova_resolver::{Builtin, DefId};
#[derive(Debug, Clone, PartialEq)]
pub enum Ty {
Int,
Float,
Bool,
Char,
String,
Bytes,
Unit,
Fn {
params: Vec<Ty>,
ret: Box<Ty>,
},
Future(Box<Ty>),
Sum {
def_id: DefId,
args: Vec<Ty>,
},
Record {
def_id: DefId,
args: Vec<Ty>,
},
Array(Box<Ty>),
Param(u32),
Assoc {
on: Box<Ty>,
assoc: DefId,
},
Var(u32),
Never,
Error,
}
impl Ty {
pub fn subst(&self, args: &[Ty]) -> Ty {
match self {
Ty::Param(i) => args.get(*i as usize).cloned().unwrap_or(Ty::Error),
Ty::Fn { params, ret } => Ty::Fn {
params: params.iter().map(|p| p.subst(args)).collect(),
ret: Box::new(ret.subst(args)),
},
Ty::Sum { def_id, args: a } => Ty::Sum {
def_id: *def_id,
args: a.iter().map(|t| t.subst(args)).collect(),
},
Ty::Record { def_id, args: a } => Ty::Record {
def_id: *def_id,
args: a.iter().map(|t| t.subst(args)).collect(),
},
Ty::Array(elem) => Ty::Array(Box::new(elem.subst(args))),
Ty::Future(out) => Ty::Future(Box::new(out.subst(args))),
Ty::Assoc { on, assoc } => Ty::Assoc {
on: Box::new(on.subst(args)),
assoc: *assoc,
},
other => other.clone(),
}
}
pub fn mentions_param(&self, idx: u32) -> bool {
match self {
Ty::Param(k) => *k == idx,
Ty::Fn { params, ret } => {
params.iter().any(|p| p.mentions_param(idx)) || ret.mentions_param(idx)
}
Ty::Sum { args, .. } | Ty::Record { args, .. } => {
args.iter().any(|a| a.mentions_param(idx))
}
Ty::Array(elem) => elem.mentions_param(idx),
Ty::Future(out) => out.mentions_param(idx),
Ty::Assoc { on, .. } => on.mentions_param(idx),
_ => false,
}
}
pub fn has_params(&self) -> bool {
match self {
Ty::Param(_) => true,
Ty::Fn { params, ret } => params.iter().any(Ty::has_params) || ret.has_params(),
Ty::Sum { args, .. } | Ty::Record { args, .. } => args.iter().any(Ty::has_params),
Ty::Array(elem) => elem.has_params(),
Ty::Future(out) => out.has_params(),
Ty::Assoc { on, .. } => on.has_params(),
_ => false,
}
}
pub fn has_vars(&self) -> bool {
match self {
Ty::Var(_) => true,
Ty::Fn { params, ret } => params.iter().any(Ty::has_vars) || ret.has_vars(),
Ty::Sum { args, .. } | Ty::Record { args, .. } => args.iter().any(Ty::has_vars),
Ty::Array(elem) => elem.has_vars(),
Ty::Future(out) => out.has_vars(),
Ty::Assoc { on, .. } => on.has_vars(),
_ => false,
}
}
pub fn has_assoc(&self) -> bool {
match self {
Ty::Assoc { .. } => true,
Ty::Fn { params, ret } => params.iter().any(Ty::has_assoc) || ret.has_assoc(),
Ty::Sum { args, .. } | Ty::Record { args, .. } => args.iter().any(Ty::has_assoc),
Ty::Array(elem) => elem.has_assoc(),
Ty::Future(out) => out.has_assoc(),
_ => false,
}
}
pub fn has_error(&self) -> bool {
match self {
Ty::Error => true,
Ty::Fn { params, ret } => params.iter().any(Ty::has_error) || ret.has_error(),
Ty::Sum { args, .. } | Ty::Record { args, .. } => args.iter().any(Ty::has_error),
Ty::Array(elem) => elem.has_error(),
Ty::Future(out) => out.has_error(),
Ty::Assoc { on, .. } => on.has_error(),
_ => false,
}
}
pub fn head(&self) -> Option<TyHead> {
match self {
Ty::Int => Some(TyHead::Int),
Ty::Float => Some(TyHead::Float),
Ty::Bool => Some(TyHead::Bool),
Ty::Char => Some(TyHead::Char),
Ty::String => Some(TyHead::String),
Ty::Bytes => Some(TyHead::Bytes),
Ty::Sum { def_id, .. } => Some(TyHead::Sum(*def_id)),
Ty::Record { def_id, .. } => Some(TyHead::Record(*def_id)),
_ => None,
}
}
pub fn match_pattern(&self, ground: &Ty, out: &mut Vec<Option<Ty>>) -> bool {
match (self, ground) {
(Ty::Param(k), g) => {
let k = *k as usize;
if out.len() <= k {
out.resize(k + 1, None);
}
match &out[k] {
Some(existing) => existing == g,
None => {
out[k] = Some(g.clone());
true
}
}
}
(
Ty::Record {
def_id: d1,
args: a1,
},
Ty::Record {
def_id: d2,
args: a2,
},
)
| (
Ty::Sum {
def_id: d1,
args: a1,
},
Ty::Sum {
def_id: d2,
args: a2,
},
) => {
d1 == d2
&& a1.len() == a2.len()
&& a1.iter().zip(a2).all(|(p, g)| p.match_pattern(g, out))
}
(Ty::Array(e1), Ty::Array(e2)) => e1.match_pattern(e2, out),
(Ty::Future(o1), Ty::Future(o2)) => o1.match_pattern(o2, out),
(
Ty::Fn {
params: p1,
ret: r1,
},
Ty::Fn {
params: p2,
ret: r2,
},
) => {
p1.len() == p2.len()
&& p1.iter().zip(p2).all(|(a, b)| a.match_pattern(b, out))
&& r1.match_pattern(r2, out)
}
(Ty::Assoc { on: p, assoc: pa }, Ty::Assoc { on: g, assoc: ga }) => {
pa == ga && p.match_pattern(g, out)
}
(a, b) => a == b,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TyHead {
Int,
Float,
Bool,
Char,
String,
Bytes,
Sum(DefId),
Record(DefId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LocalId(pub u32);
#[derive(Debug, Clone)]
pub struct Local {
pub name: String,
pub ty: Ty,
pub is_mut: bool,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct ExternFn {
pub def_id: DefId,
pub symbol: String,
pub params: Vec<Ty>,
pub ret: Ty,
pub span: Span,
}
#[derive(Debug, Default)]
pub struct Module {
pub sums: Vec<SumType>,
pub records: Vec<RecordType>,
pub traits: Vec<TraitDef>,
pub impls: Vec<ImplInfo>,
pub functions: Vec<Function>,
pub externs: Vec<ExternFn>,
}
impl Module {
pub fn extern_fn(&self, def_id: DefId) -> Option<&ExternFn> {
self.externs.iter().find(|e| e.def_id == def_id)
}
pub fn sum(&self, def_id: DefId) -> Option<&SumType> {
self.sums.iter().find(|s| s.def_id == def_id)
}
pub fn record(&self, def_id: DefId) -> Option<&RecordType> {
self.records.iter().find(|r| r.def_id == def_id)
}
pub fn trait_def(&self, def_id: DefId) -> Option<&TraitDef> {
self.traits.iter().find(|t| t.def_id == def_id)
}
pub fn function(&self, def_id: DefId) -> Option<&Function> {
self.functions.iter().find(|f| f.def_id == def_id)
}
pub fn resolve_method_full(
&self,
trait_id: DefId,
method: u32,
self_ty: &Ty,
) -> Option<(DefId, Vec<Ty>)> {
let tr = self.trait_def(trait_id)?;
let method_name = &tr.methods.get(method as usize)?.name;
let head = self_ty.head()?;
let (imp, impl_args) = self
.impls
.iter()
.filter(|i| i.trait_id == Some(trait_id) && i.self_head == head)
.find_map(|i| i.match_args(self_ty).map(|args| (i, args)))?;
if let Some((_, def)) = imp.methods.iter().find(|(n, _)| n == method_name) {
Some((*def, impl_args))
} else {
let def = tr.methods[method as usize].default_def?;
Some((def, vec![self_ty.clone()]))
}
}
}
pub const NORMALIZE_DEPTH_LIMIT: u32 = 64;
pub const NORMALIZE_STEP_LIMIT: u32 = 10_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalizeLimit {
Depth,
Steps,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NormalizeOverflow {
pub at: Ty,
pub limit: NormalizeLimit,
}
pub fn normalize_ty(ty: &Ty, impls: &[ImplInfo]) -> Result<Ty, NormalizeOverflow> {
let mut steps = NORMALIZE_STEP_LIMIT;
normalize_within(ty, impls, NORMALIZE_DEPTH_LIMIT, &mut steps).map_err(|limit| {
NormalizeOverflow {
at: ty.clone(),
limit,
}
})
}
fn normalize_within(
ty: &Ty,
impls: &[ImplInfo],
depth: u32,
steps: &mut u32,
) -> Result<Ty, NormalizeLimit> {
*steps = steps.checked_sub(1).ok_or(NormalizeLimit::Steps)?;
match ty {
Ty::Assoc { on, assoc } => {
let Some(depth) = depth.checked_sub(1) else {
return Err(NormalizeLimit::Depth);
};
let on = normalize_within(on, impls, depth, steps)?;
let Some(head) = on.head() else {
return Ok(Ty::Assoc {
on: Box::new(on),
assoc: *assoc,
});
};
let bound = impls.iter().filter(|i| i.self_head == head).find_map(|i| {
let (_, bound) = i.assoc_bindings.iter().find(|(d, _)| d == assoc)?;
let args = i.match_args(&on)?;
Some(bound.subst(&args))
});
match bound {
Some(bound) => normalize_within(&bound, impls, depth, steps),
None => Ok(Ty::Assoc {
on: Box::new(on),
assoc: *assoc,
}),
}
}
Ty::Fn { params, ret } => Ok(Ty::Fn {
params: params
.iter()
.map(|p| normalize_within(p, impls, depth, steps))
.collect::<Result<_, _>>()?,
ret: Box::new(normalize_within(ret, impls, depth, steps)?),
}),
Ty::Sum { def_id, args } => Ok(Ty::Sum {
def_id: *def_id,
args: args
.iter()
.map(|a| normalize_within(a, impls, depth, steps))
.collect::<Result<_, _>>()?,
}),
Ty::Record { def_id, args } => Ok(Ty::Record {
def_id: *def_id,
args: args
.iter()
.map(|a| normalize_within(a, impls, depth, steps))
.collect::<Result<_, _>>()?,
}),
Ty::Array(elem) => Ok(Ty::Array(Box::new(normalize_within(
elem, impls, depth, steps,
)?))),
Ty::Future(out) => Ok(Ty::Future(Box::new(normalize_within(
out, impls, depth, steps,
)?))),
Ty::Int
| Ty::Float
| Ty::Bool
| Ty::Char
| Ty::String
| Ty::Bytes
| Ty::Unit
| Ty::Param(_)
| Ty::Var(_)
| Ty::Never
| Ty::Error => Ok(ty.clone()),
}
}
pub fn self_types_overlap(a: &Ty, a_generics: u32, b: &Ty, b_generics: u32) -> bool {
match (a, b) {
(Ty::Assoc { on: a1, assoc: x }, Ty::Assoc { on: b1, assoc: y }) => {
x != y || self_types_overlap(a1, a_generics, b1, b_generics)
}
(Ty::Assoc { .. }, _) | (_, Ty::Assoc { .. }) => true,
_ => {
let b = shift_params(b, a_generics);
let mut subst: Vec<Option<Ty>> = vec![None; (a_generics + b_generics) as usize];
unify_patterns(a, &b, &mut subst)
}
}
}
fn shift_params(t: &Ty, by: u32) -> Ty {
match t {
Ty::Param(k) => Ty::Param(k + by),
Ty::Fn { params, ret } => Ty::Fn {
params: params.iter().map(|p| shift_params(p, by)).collect(),
ret: Box::new(shift_params(ret, by)),
},
Ty::Sum { def_id, args } => Ty::Sum {
def_id: *def_id,
args: args.iter().map(|a| shift_params(a, by)).collect(),
},
Ty::Record { def_id, args } => Ty::Record {
def_id: *def_id,
args: args.iter().map(|a| shift_params(a, by)).collect(),
},
Ty::Array(elem) => Ty::Array(Box::new(shift_params(elem, by))),
Ty::Future(out) => Ty::Future(Box::new(shift_params(out, by))),
other => other.clone(),
}
}
fn walk_param(t: &Ty, subst: &[Option<Ty>]) -> Ty {
match t {
Ty::Param(k) => match subst.get(*k as usize).and_then(|o| o.as_ref()) {
Some(bound) => walk_param(bound, subst),
None => t.clone(),
},
_ => t.clone(),
}
}
fn occurs(k: u32, t: &Ty, subst: &[Option<Ty>]) -> bool {
match walk_param(t, subst) {
Ty::Param(j) => j == k,
Ty::Fn { params, ret } => {
params.iter().any(|p| occurs(k, p, subst)) || occurs(k, &ret, subst)
}
Ty::Sum { args, .. } | Ty::Record { args, .. } => args.iter().any(|a| occurs(k, a, subst)),
Ty::Array(elem) => occurs(k, &elem, subst),
Ty::Future(out) => occurs(k, &out, subst),
_ => false,
}
}
fn unify_patterns(a: &Ty, b: &Ty, subst: &mut Vec<Option<Ty>>) -> bool {
let a = walk_param(a, subst);
let b = walk_param(b, subst);
match (&a, &b) {
(Ty::Param(i), Ty::Param(j)) if i == j => true,
(Ty::Param(i), _) => {
if occurs(*i, &b, subst) {
return false;
}
subst[*i as usize] = Some(b.clone());
true
}
(_, Ty::Param(j)) => {
if occurs(*j, &a, subst) {
return false;
}
subst[*j as usize] = Some(a.clone());
true
}
(
Ty::Record {
def_id: d1,
args: a1,
},
Ty::Record {
def_id: d2,
args: a2,
},
)
| (
Ty::Sum {
def_id: d1,
args: a1,
},
Ty::Sum {
def_id: d2,
args: a2,
},
) => {
d1 == d2
&& a1.len() == a2.len()
&& a1.iter().zip(a2).all(|(x, y)| unify_patterns(x, y, subst))
}
(Ty::Array(e1), Ty::Array(e2)) => unify_patterns(e1, e2, subst),
(Ty::Future(o1), Ty::Future(o2)) => unify_patterns(o1, o2, subst),
(
Ty::Fn {
params: p1,
ret: r1,
},
Ty::Fn {
params: p2,
ret: r2,
},
) => {
p1.len() == p2.len()
&& p1.iter().zip(p2).all(|(x, y)| unify_patterns(x, y, subst))
&& unify_patterns(r1, r2, subst)
}
(x, y) => x == y,
}
}
#[derive(Debug, Clone)]
pub struct TraitDef {
pub def_id: DefId,
pub name: String,
pub supertraits: Vec<DefId>,
pub methods: Vec<TraitMethod>,
pub assoc_types: Vec<(String, DefId)>,
}
#[derive(Debug, Clone)]
pub struct TraitMethod {
pub name: String,
pub params: Vec<Ty>,
pub ret: Ty,
pub has_self: bool,
pub mut_self: bool,
pub generics: u32,
pub bounds: Vec<Vec<DefId>>,
pub default_def: Option<DefId>,
}
#[derive(Debug, Clone)]
pub struct ImplInfo {
pub trait_id: Option<DefId>,
pub self_head: TyHead,
pub self_ty: Ty,
pub generics: u32,
pub bounds: Vec<Vec<DefId>>,
pub methods: Vec<(String, DefId)>,
pub assoc_bindings: Vec<(DefId, Ty)>,
}
impl ImplInfo {
pub fn match_args(&self, concrete: &Ty) -> Option<Vec<Ty>> {
let mut out: Vec<Option<Ty>> = Vec::new();
if !self.self_ty.match_pattern(concrete, &mut out) {
return None;
}
Some(
(0..self.generics as usize)
.map(|i| out.get(i).cloned().flatten().unwrap_or(Ty::Error))
.collect(),
)
}
}
#[derive(Debug, Clone)]
pub struct RecordType {
pub def_id: DefId,
pub name: String,
pub generics: u32,
pub fields: Vec<RecordField>,
}
#[derive(Debug, Clone)]
pub struct RecordField {
pub name: String,
pub ty: Ty,
}
#[derive(Debug, Clone)]
pub struct SumType {
pub def_id: DefId,
pub name: String,
pub generics: u32,
pub variants: Vec<Variant>,
}
#[derive(Debug, Clone)]
pub struct Variant {
pub name: String,
pub fields: Vec<Ty>,
}
#[derive(Debug)]
pub struct Function {
pub def_id: DefId,
pub name: String,
pub generics: u32,
pub bounds: Vec<Vec<DefId>>,
pub takes_env: bool,
pub capture_count: u32,
pub params: u32,
pub locals: Vec<Local>,
pub ret_ty: Ty,
pub is_async: bool,
pub body: Expr,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct Expr {
pub kind: ExprKind,
pub ty: Ty,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Callee {
Def(DefId),
Builtin(Builtin),
Local(LocalId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Rem,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnOp {
Neg,
Not,
BitNot,
}
#[derive(Debug, Clone)]
pub enum ExprKind {
IntLit(i64),
FloatLit(f64),
BoolLit(bool),
StrLit(String),
CharLit(char),
Unit,
Local(LocalId),
MakeClosure {
func: DefId,
type_args: Vec<Ty>,
captures: Vec<Expr>,
},
Call {
func: Callee,
type_args: Vec<Ty>,
args: Vec<Expr>,
},
MakeVariant {
sum: DefId,
variant: u32,
args: Vec<Expr>,
},
MakeRecord {
record: DefId,
fields: Vec<Expr>,
},
FieldGet {
target: Box<Expr>,
index: u32,
},
FieldSet {
target: Box<Expr>,
index: u32,
value: Box<Expr>,
},
MakeArray {
elems: Vec<Expr>,
},
ArrayRepeat {
init: Box<Expr>,
len: Box<Expr>,
},
Index {
target: Box<Expr>,
index: Box<Expr>,
},
IndexSet {
target: Box<Expr>,
index: Box<Expr>,
value: Box<Expr>,
},
ArrayLen {
target: Box<Expr>,
},
TraitCall {
trait_id: DefId,
method: u32,
self_ty: Ty,
type_args: Vec<Ty>,
receiver: Option<Box<Expr>>,
args: Vec<Expr>,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
LogicalAnd {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
LogicalOr {
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Unary {
op: UnOp,
expr: Box<Expr>,
},
Let {
local: LocalId,
init: Box<Expr>,
},
Assign {
local: LocalId,
value: Box<Expr>,
},
Block {
stmts: Vec<Expr>,
trailing: Option<Box<Expr>>,
},
If {
cond: Box<Expr>,
then: Box<Expr>,
else_: Option<Box<Expr>>,
},
While {
cond: Box<Expr>,
body: Box<Expr>,
},
Break,
Continue,
Match {
scrutinee: Box<Expr>,
arms: Vec<Arm>,
},
Return(Option<Box<Expr>>),
ToStr(Box<Expr>),
StrConcat(Vec<Expr>),
Await(Box<Expr>),
}
#[derive(Debug, Clone)]
pub struct Arm {
pub pattern: Pattern,
pub body: Expr,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum Pattern {
Wildcard,
Bind(LocalId),
LitInt(i64),
LitBool(bool),
LitStr(String),
Variant {
sum: DefId,
variant: u32,
binders: Vec<Option<LocalId>>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subst_recurses_into_a_projection_without_normalizing() {
use nova_resolver::DefId;
let proj = Ty::Assoc {
on: Box::new(Ty::Param(0)),
assoc: DefId(7),
};
assert_eq!(
proj.subst(&[Ty::Int]),
Ty::Assoc {
on: Box::new(Ty::Int),
assoc: DefId(7)
}
);
}
#[test]
fn a_projection_has_no_head_and_no_param_of_its_own() {
use nova_resolver::DefId;
let proj = Ty::Assoc {
on: Box::new(Ty::Param(2)),
assoc: DefId(7),
};
assert!(proj.head().is_none());
assert!(proj.mentions_param(2));
assert!(!proj.mentions_param(0));
}
#[test]
fn a_projection_reports_the_params_and_vars_inside_its_self_type() {
use nova_resolver::DefId;
let on_param = Ty::Assoc {
on: Box::new(Ty::Param(0)),
assoc: DefId(7),
};
assert!(on_param.has_params(), "a projection on a Param is generic");
assert!(!on_param.has_vars());
let on_var = Ty::Assoc {
on: Box::new(Ty::Var(3)),
assoc: DefId(7),
};
assert!(
on_var.has_vars(),
"a projection on a Var still contains a var"
);
assert!(!on_var.has_params());
let concrete = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: DefId(7),
};
assert!(!concrete.has_params());
assert!(!concrete.has_vars());
}
#[test]
fn self_types_overlap_treats_a_projection_conservatively() {
use nova_resolver::DefId;
let item = DefId(7);
let other = DefId(8);
let on_int = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: item,
};
let on_bool = Ty::Assoc {
on: Box::new(Ty::Bool),
assoc: item,
};
assert!(
!self_types_overlap(&on_int, 0, &on_bool, 0),
"same associated type, but Int and Bool can never be the same Self type"
);
let on_param = Ty::Assoc {
on: Box::new(Ty::Param(0)),
assoc: item,
};
assert!(
self_types_overlap(&on_param, 1, &on_int, 0),
"Param(0) can be instantiated to Int, so these can coincide"
);
let other_assoc = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: other,
};
assert!(
self_types_overlap(&on_int, 0, &other_assoc, 0),
"different associated types cannot be proven disjoint, so assume overlap"
);
assert!(self_types_overlap(&on_int, 0, &Ty::Int, 0));
assert!(self_types_overlap(&Ty::Bool, 0, &on_int, 0));
}
#[test]
fn match_pattern_treats_assoc_structurally() {
use nova_resolver::DefId;
let item = DefId(7);
let other = DefId(8);
let pattern = Ty::Assoc {
on: Box::new(Ty::Param(0)),
assoc: item,
};
let ground = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: item,
};
let mut out = Vec::new();
assert!(pattern.match_pattern(&ground, &mut out));
assert_eq!(out, vec![Some(Ty::Int)]);
let different_assoc = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: other,
};
let same_self = Ty::Assoc {
on: Box::new(Ty::Int),
assoc: item,
};
assert!(!same_self.match_pattern(&different_assoc, &mut Vec::new()));
assert!(!pattern.match_pattern(&Ty::Int, &mut Vec::new()));
assert!(!Ty::Int.match_pattern(&ground, &mut Vec::new()));
}
#[test]
fn has_assoc_finds_a_projection_at_every_nesting_depth() {
use nova_resolver::DefId;
let p = || Ty::Assoc {
on: Box::new(Ty::Int),
assoc: DefId(7),
};
assert!(p().has_assoc());
assert!(Ty::Array(Box::new(p())).has_assoc());
assert!(
Ty::Future(Box::new(p())).has_assoc(),
"a future's output counts too, not only an array's element"
);
assert!(
Ty::Record {
def_id: DefId(1),
args: vec![Ty::Bool, p()],
}
.has_assoc(),
"not only the first argument"
);
assert!(Ty::Sum {
def_id: DefId(1),
args: vec![p()],
}
.has_assoc());
assert!(
Ty::Fn {
params: vec![Ty::Int],
ret: Box::new(p()),
}
.has_assoc(),
"the return type counts, not only the parameters"
);
assert!(Ty::Fn {
params: vec![p()],
ret: Box::new(Ty::Int),
}
.has_assoc());
assert!(!Ty::Int.has_assoc());
assert!(!Ty::Param(0).has_assoc());
assert!(!Ty::Array(Box::new(Ty::Int)).has_assoc());
assert!(!Ty::Future(Box::new(Ty::Int)).has_assoc());
assert!(!Ty::Fn {
params: vec![Ty::Int],
ret: Box::new(Ty::Bool),
}
.has_assoc());
}
#[test]
fn future_recurses_into_its_output_type() {
let p = || Ty::Param(0);
assert!(Ty::Future(Box::new(p())).has_params());
assert!(!Ty::Future(Box::new(Ty::Int)).has_params());
assert!(Ty::Future(Box::new(Ty::Var(0))).has_vars());
assert!(!Ty::Future(Box::new(Ty::Int)).has_vars());
assert!(Ty::Future(Box::new(Ty::Error)).has_error());
assert!(!Ty::Future(Box::new(Ty::Int)).has_error());
assert!(Ty::Future(Box::new(p())).mentions_param(0));
assert!(!Ty::Future(Box::new(p())).mentions_param(1));
}
#[test]
fn future_substitutes_its_output_type() {
let subbed = Ty::Future(Box::new(Ty::Param(0))).subst(&[Ty::Float]);
assert_eq!(subbed, Ty::Future(Box::new(Ty::Float)));
}
#[test]
fn future_match_pattern_binds_through_the_output() {
let mut out = Vec::new();
assert!(Ty::Future(Box::new(Ty::Param(0)))
.match_pattern(&Ty::Future(Box::new(Ty::Bool)), &mut out));
assert_eq!(out, vec![Some(Ty::Bool)]);
let mut out2 = Vec::new();
assert!(!Ty::Future(Box::new(Ty::Param(0)))
.match_pattern(&Ty::Array(Box::new(Ty::Bool)), &mut out2));
}
fn imp(self_ty: Ty, generics: u32, bindings: Vec<(DefId, Ty)>) -> ImplInfo {
ImplInfo {
trait_id: None,
self_head: self_ty.head().expect("test self types have a head"),
self_ty,
generics,
bounds: Vec::new(),
methods: Vec::new(),
assoc_bindings: bindings,
}
}
fn rec(def: u32, args: Vec<Ty>) -> Ty {
Ty::Record {
def_id: DefId(def),
args,
}
}
fn proj(on: Ty, assoc: u32) -> Ty {
Ty::Assoc {
on: Box::new(on),
assoc: DefId(assoc),
}
}
const ITEM: u32 = 7;
#[test]
fn normalize_resolves_a_projection_through_the_impls_own_arguments() {
let impls = [imp(
rec(1, vec![Ty::Param(0)]),
1,
vec![(DefId(ITEM), Ty::Param(0))],
)];
assert_eq!(
normalize_ty(&proj(rec(1, vec![Ty::Int]), ITEM), &impls),
Ok(Ty::Int)
);
}
#[test]
fn normalize_picks_the_impl_whose_self_type_matches() {
let impls = [
imp(
rec(1, vec![Ty::Param(0)]),
1,
vec![(DefId(ITEM), Ty::Param(0))],
),
imp(rec(2, vec![]), 0, vec![(DefId(ITEM), Ty::Bool)]),
];
assert_eq!(
normalize_ty(&proj(rec(1, vec![Ty::Char]), ITEM), &impls),
Ok(Ty::Char),
"W<Char>::Item comes from the W impl"
);
assert_eq!(
normalize_ty(&proj(rec(2, vec![]), ITEM), &impls),
Ok(Ty::Bool),
"K::Item comes from the K impl"
);
}
#[test]
fn normalize_rejects_an_impl_that_shares_the_head_but_does_not_fit() {
let pair = |a: Ty, b: Ty| rec(1, vec![a, b]);
let impls = [
imp(
pair(Ty::Param(0), Ty::Param(0)),
1,
vec![(DefId(ITEM), Ty::Param(0))],
),
imp(pair(Ty::Int, Ty::Bool), 0, vec![(DefId(ITEM), Ty::String)]),
];
assert_eq!(
normalize_ty(&proj(pair(Ty::Int, Ty::Bool), ITEM), &impls),
Ok(Ty::String),
"Pair<Int, Bool> does not fit Pair<T, T>, so the concrete impl answers"
);
assert_eq!(
normalize_ty(&proj(pair(Ty::Int, Ty::Int), ITEM), &impls),
Ok(Ty::Int),
"Pair<Int, Int> does fit Pair<T, T>"
);
}
#[test]
fn normalize_resolves_a_projection_whose_self_type_is_itself_a_projection() {
let mid = 9;
let w = rec(1, vec![]);
let k = rec(2, vec![]);
let impls = [
imp(w.clone(), 0, vec![(DefId(mid), k)]),
imp(rec(2, vec![]), 0, vec![(DefId(ITEM), Ty::Bool)]),
];
assert_eq!(
normalize_ty(&proj(proj(w, mid), ITEM), &impls),
Ok(Ty::Bool)
);
}
#[test]
fn normalize_only_reads_the_binding_for_the_projected_associated_type() {
let other = 8;
let impls = [imp(
rec(1, vec![]),
0,
vec![(DefId(ITEM), Ty::Int), (DefId(other), Ty::String)],
)];
assert_eq!(
normalize_ty(&proj(rec(1, vec![]), ITEM), &impls),
Ok(Ty::Int)
);
assert_eq!(
normalize_ty(&proj(rec(1, vec![]), other), &impls),
Ok(Ty::String)
);
}
#[test]
fn normalize_leaves_a_projection_on_a_generic_parameter_alone() {
let impls = [imp(rec(1, vec![]), 0, vec![(DefId(ITEM), Ty::Int)])];
let abstract_proj = proj(Ty::Param(0), ITEM);
assert_eq!(normalize_ty(&abstract_proj, &impls), Ok(abstract_proj));
}
#[test]
fn normalize_leaves_a_projection_with_no_matching_impl_alone() {
let impls = [imp(rec(1, vec![]), 0, vec![(DefId(ITEM), Ty::Int)])];
let unresolvable = proj(Ty::Bool, ITEM);
assert_eq!(normalize_ty(&unresolvable, &impls), Ok(unresolvable));
}
#[test]
fn normalize_recurses_into_every_compound_type() {
let impls = [
imp(rec(1, vec![]), 0, vec![(DefId(ITEM), Ty::Int)]),
imp(rec(2, vec![Ty::Param(0)]), 1, vec![(DefId(ITEM), Ty::Char)]),
];
let p = || proj(rec(1, vec![]), ITEM);
assert_eq!(
normalize_ty(&Ty::Array(Box::new(p())), &impls),
Ok(Ty::Array(Box::new(Ty::Int)))
);
assert_eq!(
normalize_ty(&Ty::Future(Box::new(p())), &impls),
Ok(Ty::Future(Box::new(Ty::Int)))
);
assert_eq!(
normalize_ty(&rec(3, vec![p(), Ty::Bool]), &impls),
Ok(rec(3, vec![Ty::Int, Ty::Bool]))
);
assert_eq!(
normalize_ty(
&Ty::Sum {
def_id: DefId(4),
args: vec![p()]
},
&impls
),
Ok(Ty::Sum {
def_id: DefId(4),
args: vec![Ty::Int]
})
);
assert_eq!(
normalize_ty(
&Ty::Fn {
params: vec![p(), Ty::Bool],
ret: Box::new(p())
},
&impls
),
Ok(Ty::Fn {
params: vec![Ty::Int, Ty::Bool],
ret: Box::new(Ty::Int)
})
);
assert_eq!(
normalize_ty(&proj(rec(2, vec![p()]), ITEM), &impls),
Ok(Ty::Char),
"the inner W::Item resolves to Int, making the outer K<Int>::Item"
);
}
#[test]
fn normalize_follows_a_chain_of_bindings_to_the_end() {
let a = 7;
let b = 8;
let self_ty = rec(1, vec![]);
let impls = [imp(
self_ty.clone(),
0,
vec![(DefId(a), proj(self_ty.clone(), b)), (DefId(b), Ty::Int)],
)];
assert_eq!(normalize_ty(&proj(self_ty, a), &impls), Ok(Ty::Int));
}
#[test]
fn normalize_gives_up_on_a_cyclic_binding_instead_of_diverging() {
let self_ty = rec(1, vec![]);
let cycle = proj(self_ty.clone(), ITEM);
let impls = [imp(self_ty, 0, vec![(DefId(ITEM), cycle.clone())])];
assert_eq!(
normalize_ty(&cycle, &impls),
Err(NormalizeOverflow {
at: cycle,
limit: NormalizeLimit::Depth
})
);
}
#[test]
fn normalize_gives_up_on_a_binding_that_grows_without_bound() {
let self_ty = rec(1, vec![]);
let grows = Ty::Array(Box::new(proj(self_ty.clone(), ITEM)));
let impls = [imp(self_ty.clone(), 0, vec![(DefId(ITEM), grows)])];
assert_eq!(
normalize_ty(&proj(self_ty.clone(), ITEM), &impls),
Err(NormalizeOverflow {
at: proj(self_ty, ITEM),
limit: NormalizeLimit::Depth
}),
"reports overflow rather than diverging"
);
}
#[test]
fn normalize_admits_a_chain_longer_than_a_couple_of_links() {
let self_ty = rec(1, vec![]);
let n = 16u32;
let mut bindings: Vec<(DefId, Ty)> = (0..n)
.map(|i| (DefId(100 + i), proj(self_ty.clone(), 101 + i)))
.collect();
bindings.push((DefId(100 + n), Ty::Int));
let impls = [imp(self_ty.clone(), 0, bindings)];
assert_eq!(normalize_ty(&proj(self_ty, 100), &impls), Ok(Ty::Int));
}
#[test]
fn normalize_bounds_total_work_not_only_chain_depth() {
let n = 32u32;
let self_ty = rec(1, vec![]);
let pair = |a: Ty, b: Ty| rec(2, vec![a, b]);
let mut bindings: Vec<(DefId, Ty)> = (0..n)
.map(|i| {
let next = proj(self_ty.clone(), 101 + i);
(DefId(100 + i), pair(next.clone(), next))
})
.collect();
bindings.push((DefId(100 + n), Ty::Int));
let impls = [imp(self_ty.clone(), 0, bindings)];
let input = proj(self_ty, 100);
assert_eq!(
normalize_ty(&input, &impls),
Err(NormalizeOverflow {
at: input,
limit: NormalizeLimit::Steps
}),
"a diagnostic, not a hang — and not `Ty::Error`, which unifies with \
anything and would make this a wrong answer instead"
);
}
#[test]
fn normalize_admits_a_branching_chain_that_stays_within_the_allowance() {
let n = 10u32;
let self_ty = rec(1, vec![]);
let pair = |a: Ty, b: Ty| rec(2, vec![a, b]);
let mut bindings: Vec<(DefId, Ty)> = (0..n)
.map(|i| {
let next = proj(self_ty.clone(), 101 + i);
(DefId(100 + i), pair(next.clone(), next))
})
.collect();
bindings.push((DefId(100 + n), Ty::Int));
let impls = [imp(self_ty.clone(), 0, bindings)];
let mut expected = Ty::Int;
for _ in 0..n {
expected = pair(expected.clone(), expected);
}
assert_eq!(normalize_ty(&proj(self_ty, 100), &impls), Ok(expected));
}
}