use std::collections::{HashMap, HashSet, VecDeque};
use bock_air::{AIRNode, NodeKind};
use bock_ast::TypePath;
use bock_errors::{DiagnosticBag, DiagnosticCode};
use crate::{GenericType, NamedType, PrimitiveType, Type};
const E_COHERENCE_OVERLAP: DiagnosticCode = DiagnosticCode {
prefix: 'E',
number: 4010,
};
const E_SEALED_PRIMITIVE_IMPL: DiagnosticCode = DiagnosticCode {
prefix: 'E',
number: 4011,
};
const E_DUPLICATE_METHOD: DiagnosticCode = DiagnosticCode {
prefix: 'E',
number: 4012,
};
pub type ImplId = u32;
#[derive(Debug, Clone, PartialEq)]
pub struct TraitRef {
pub name: String,
pub args: Vec<Type>,
}
impl TraitRef {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
args: vec![],
}
}
#[must_use]
pub fn parameterized(name: impl Into<String>, args: Vec<Type>) -> Self {
Self {
name: name.into(),
args,
}
}
fn from_path(path: &TypePath) -> Self {
let name = path
.segments
.iter()
.map(|s| s.name.as_str())
.collect::<Vec<_>>()
.join(".");
Self { name, args: vec![] }
}
}
#[derive(Debug, Clone)]
pub struct ResolvedMethod {
pub impl_id: ImplId,
pub trait_ref: Option<TraitRef>,
pub method: String,
}
#[derive(Debug, Clone)]
pub struct ImplEntry {
pub id: ImplId,
pub trait_ref: Option<TraitRef>,
pub type_key: String,
pub methods: Vec<String>,
pub is_generic: bool,
pub is_canonical: bool,
pub trait_args: Vec<Type>,
pub is_derived: bool,
pub target_type: Option<Type>,
}
pub struct ImplTable {
entries: HashMap<ImplId, ImplEntry>,
trait_impl_index: HashMap<(String, String), ImplId>,
param_trait_impl_index: HashMap<(String, String, String), ImplId>,
inherent_impl_index: HashMap<String, ImplId>,
supertraits: HashMap<String, Vec<String>>,
assoc_types: HashMap<(ImplId, String), Type>,
next_id: u32,
pub diags: DiagnosticBag,
method_defs: HashMap<String, Vec<MethodDef>>,
}
struct MethodDef {
name: String,
sig_key: String,
span: bock_errors::Span,
origin: String,
}
impl ImplTable {
#[must_use]
pub fn new() -> Self {
Self {
entries: HashMap::new(),
trait_impl_index: HashMap::new(),
param_trait_impl_index: HashMap::new(),
inherent_impl_index: HashMap::new(),
supertraits: HashMap::new(),
assoc_types: HashMap::new(),
next_id: 0,
diags: DiagnosticBag::new(),
method_defs: HashMap::new(),
}
}
#[must_use]
pub fn build_from(module: &AIRNode) -> Self {
let mut table = Self::new();
if let NodeKind::Module { items, .. } = &module.kind {
for item in items {
table.visit_item(item);
}
}
table.check_method_namespace();
table.synthesize_blanket_into();
table
}
fn check_method_namespace(&mut self) {
let mut type_keys: Vec<&String> = self.method_defs.keys().collect();
type_keys.sort();
let type_keys: Vec<String> = type_keys.into_iter().cloned().collect();
for type_key in type_keys {
let defs = &self.method_defs[&type_key];
let mut by_name: HashMap<&str, Vec<usize>> = HashMap::new();
for (idx, def) in defs.iter().enumerate() {
by_name.entry(def.name.as_str()).or_default().push(idx);
}
let mut names: Vec<&str> = by_name.keys().copied().collect();
names.sort();
for name in names {
let indices = &by_name[name];
if indices.len() < 2 {
continue;
}
let first = &defs[indices[0]];
for &dup_idx in &indices[1..] {
let dup = &defs[dup_idx];
let same_sig = dup.sig_key == first.sig_key;
let detail = if same_sig {
format!(
"a method named `{name}` is already defined for type `{type_key}` \
in the {}; a type has one method namespace, so the same method \
may not be defined twice",
first.origin,
)
} else {
format!(
"method `{name}` is defined for type `{type_key}` with conflicting \
signatures (in the {} and the {}); a type has one method slot per \
name and cannot satisfy two requirements with incompatible signatures",
first.origin, dup.origin,
)
};
self.diags
.error(E_DUPLICATE_METHOD, detail, dup.span)
.note(format!(
"a trait requirement is satisfied by a matching method anywhere in \
the type's namespace; if `{name}` should satisfy a trait, define it \
once (as an inherent/class-body method or inside the trait impl) and \
leave the other block empty",
));
}
}
}
}
fn record_method_def(&mut self, type_key: &str, method: &AIRNode, origin: &str) {
if let NodeKind::FnDecl { name, .. } = &method.kind {
self.method_defs
.entry(type_key.to_owned())
.or_default()
.push(MethodDef {
name: name.name.clone(),
sig_key: method_sig_key(method),
span: method.span,
origin: origin.to_owned(),
});
}
}
fn synthesize_blanket_into(&mut self) {
let froms: Vec<(Type, Type)> = self
.entries
.values()
.filter_map(|e| {
let tr = e.trait_ref.as_ref()?;
if tr.name != "From" || tr.args.len() != 1 || e.is_generic {
return None;
}
let source = tr.args[0].clone();
let target = e.target_type.clone()?;
Some((source, target))
})
.collect();
for (source, target) in froms {
let into_arg_key = trait_arg_key(std::slice::from_ref(&target));
let into_target_key = type_key(&source);
let occupied = self.param_trait_impl_index.contains_key(&(
"Into".to_owned(),
into_arg_key,
into_target_key,
));
if occupied {
continue;
}
self.register_param_trait_impl("Into", std::slice::from_ref(&target), &source, true);
}
}
fn visit_item(&mut self, node: &AIRNode) {
match &node.kind {
NodeKind::ImplBlock {
trait_path,
trait_args,
target,
methods,
generic_params,
..
} => {
let resolved_trait_args: Vec<Type> =
trait_args.iter().map(type_from_node).collect();
let trait_ref = trait_path.as_ref().map(|p| {
let mut tr = TraitRef::from_path(p);
tr.args = resolved_trait_args.clone();
tr
});
let type_key = type_key_from_node(target);
let is_generic = !generic_params.is_empty();
if let Some(tr) = &trait_ref {
if SEALED_CORE_TRAITS.contains(&tr.name.as_str())
&& SEALED_PRIMITIVE_KEYS.contains(&type_key.as_str())
{
self.diags
.error(
E_SEALED_PRIMITIVE_IMPL,
format!(
"cannot implement core trait `{}` for primitive type `{}`: its conformance is provided by the compiler and is sealed",
tr.name, type_key,
),
node.span,
)
.note(format!(
"wrap `{type_key}` in a newtype (e.g. `record My{type_key} {{ value: {type_key} }}`) and implement `{}` for that instead",
tr.name,
));
return;
}
}
if !is_generic {
if let Some(tr) = &trait_ref {
if tr.args.is_empty() {
let index_key = (tr.name.clone(), type_key.clone());
if self.trait_impl_index.contains_key(&index_key) {
self.diags.error(
E_COHERENCE_OVERLAP,
format!(
"conflicting implementations of trait `{}` for type `{}`",
tr.name, type_key,
),
node.span,
);
return;
}
} else {
let arg_key = trait_arg_key(&tr.args);
let index_key = (tr.name.clone(), arg_key.clone(), type_key.clone());
if self.param_trait_impl_index.contains_key(&index_key) {
self.diags.error(
E_COHERENCE_OVERLAP,
format!(
"conflicting implementations of trait `{}[{}]` for type `{}`",
tr.name, arg_key, type_key,
),
node.span,
);
return;
}
}
}
}
let id = self.alloc_id();
let origin = match &trait_ref {
Some(tr) => format!("`impl {} for {}` block", tr.name, type_key),
None => format!("inherent `impl {type_key}` block"),
};
let trait_is_parameterized =
trait_ref.as_ref().is_some_and(|tr| !tr.args.is_empty());
let track_namespace = !is_generic && !trait_is_parameterized;
let mut method_names = Vec::new();
for m in methods {
match &m.kind {
NodeKind::FnDecl { name, .. } => {
method_names.push(name.name.clone());
if track_namespace {
self.record_method_def(&type_key, m, &origin);
}
}
NodeKind::TypeAlias { name, ty, .. } => {
let resolved = type_from_node(ty);
self.assoc_types.insert((id, name.name.clone()), resolved);
}
_ => {}
}
}
if let Some(tr) = &trait_ref {
if !is_generic {
if tr.args.is_empty() {
self.trait_impl_index
.insert((tr.name.clone(), type_key.clone()), id);
} else {
self.param_trait_impl_index.insert(
(tr.name.clone(), trait_arg_key(&tr.args), type_key.clone()),
id,
);
}
}
} else {
self.inherent_impl_index.insert(type_key.clone(), id);
}
let target_type = Some(type_from_node(target));
self.entries.insert(
id,
ImplEntry {
id,
trait_ref,
type_key,
methods: method_names,
is_generic,
is_canonical: false,
trait_args: resolved_trait_args,
is_derived: false,
target_type,
},
);
}
NodeKind::TraitDecl {
name,
generic_params,
..
} => {
for param in generic_params {
if param.name.name == "Self" {
for bound in ¶m.bounds {
let supertrait = trait_name_from_path(bound);
self.register_supertrait(name.name.clone(), supertrait);
}
}
}
}
NodeKind::ClassDecl { name, methods, .. } => {
let class_key = name.name.clone();
let origin = format!("`class {class_key}` body");
for m in methods {
if matches!(m.kind, NodeKind::FnDecl { .. }) {
self.record_method_def(&class_key, m, &origin);
}
}
}
_ => {}
}
}
fn alloc_id(&mut self) -> ImplId {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn register_supertrait(
&mut self,
sub_trait: impl Into<String>,
super_trait: impl Into<String>,
) {
self.supertraits
.entry(sub_trait.into())
.or_default()
.push(super_trait.into());
}
pub fn register_trait_impl(&mut self, trait_name: impl Into<String>, ty: &Type) -> ImplId {
self.register_trait_impl_inner(trait_name, &[], ty, false, false)
}
pub fn register_param_trait_impl(
&mut self,
trait_name: impl Into<String>,
trait_args: &[Type],
ty: &Type,
is_derived: bool,
) -> ImplId {
self.register_trait_impl_inner(trait_name, trait_args, ty, false, is_derived)
}
fn register_trait_impl_inner(
&mut self,
trait_name: impl Into<String>,
trait_args: &[Type],
ty: &Type,
is_canonical: bool,
is_derived: bool,
) -> ImplId {
let id = self.alloc_id();
let trait_name = trait_name.into();
let key = type_key(ty);
let args_vec = trait_args.to_vec();
let trait_ref = if args_vec.is_empty() {
TraitRef::new(&trait_name)
} else {
TraitRef::parameterized(&trait_name, args_vec.clone())
};
self.entries.insert(
id,
ImplEntry {
id,
trait_ref: Some(trait_ref),
type_key: key.clone(),
methods: vec![],
is_generic: false,
is_canonical,
trait_args: args_vec.clone(),
is_derived,
target_type: Some(ty.clone()),
},
);
if args_vec.is_empty() {
self.trait_impl_index.insert((trait_name, key), id);
} else {
self.param_trait_impl_index
.insert((trait_name, trait_arg_key(&args_vec), key), id);
}
id
}
pub fn fold_imported_impls(&mut self, impls: &[(String, Vec<Type>, Type)]) {
for (trait_name, trait_args, target) in impls {
let key = type_key(target);
if trait_args.is_empty() {
if self
.trait_impl_index
.contains_key(&(trait_name.clone(), key.clone()))
{
continue; }
self.register_trait_impl(trait_name.clone(), target);
} else {
let arg_key = trait_arg_key(trait_args);
if self
.param_trait_impl_index
.contains_key(&(trait_name.clone(), arg_key, key))
{
continue;
}
self.register_param_trait_impl(trait_name.clone(), trait_args, target, false);
}
}
self.synthesize_blanket_into();
}
pub fn register_assoc_type(&mut self, impl_id: ImplId, name: impl Into<String>, ty: Type) {
self.assoc_types.insert((impl_id, name.into()), ty);
}
#[must_use]
pub fn resolve_assoc_type(&self, impl_id: ImplId, name: &str) -> Option<&Type> {
self.assoc_types.get(&(impl_id, name.to_owned()))
}
#[must_use]
pub fn all_supertraits(&self, trait_name: &str) -> Vec<String> {
let mut result = Vec::new();
let mut visited: HashSet<String> = HashSet::new();
let mut queue: VecDeque<String> = VecDeque::new();
if let Some(direct) = self.supertraits.get(trait_name) {
for st in direct {
if visited.insert(st.clone()) {
queue.push_back(st.clone());
}
}
}
while let Some(name) = queue.pop_front() {
result.push(name.clone());
if let Some(supers) = self.supertraits.get(&name) {
for st in supers {
if visited.insert(st.clone()) {
queue.push_back(st.clone());
}
}
}
}
result
}
#[must_use]
pub fn get_entry(&self, id: ImplId) -> Option<&ImplEntry> {
self.entries.get(&id)
}
pub fn entries(&self) -> impl Iterator<Item = &ImplEntry> {
self.entries.values()
}
#[must_use]
pub fn exportable_trait_impls(&self) -> Vec<(String, Vec<Type>, Type)> {
let mut out: Vec<(String, Vec<Type>, Type)> = Vec::new();
for entry in self.entries.values() {
if entry.is_canonical || entry.is_derived || entry.is_generic {
continue;
}
let Some(tr) = &entry.trait_ref else {
continue; };
let Some(target) = &entry.target_type else {
continue; };
if matches!(target, Type::Primitive(_)) {
continue; }
out.push((tr.name.clone(), entry.trait_args.clone(), target.clone()));
}
out
}
fn find_trait_impl(&self, trait_name: &str, type_key: &str) -> Option<ImplId> {
self.trait_impl_index
.get(&(trait_name.to_owned(), type_key.to_owned()))
.copied()
}
fn find_param_trait_impl(
&self,
trait_name: &str,
trait_arg_key: &str,
type_key: &str,
) -> Option<ImplId> {
self.param_trait_impl_index
.get(&(
trait_name.to_owned(),
trait_arg_key.to_owned(),
type_key.to_owned(),
))
.copied()
}
#[must_use]
pub fn has_any_param_trait_impl(&self, trait_name: &str, type_key: &str) -> bool {
self.param_trait_impl_index
.keys()
.any(|(t, _arg, ty)| t == trait_name && ty == type_key)
}
fn find_inherent_impl(&self, type_key: &str) -> Option<ImplId> {
self.inherent_impl_index.get(type_key).copied()
}
}
impl Default for ImplTable {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn resolve_impl(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> Option<ImplId> {
let key = type_key(ty);
if trait_ref.args.is_empty() {
impls.find_trait_impl(&trait_ref.name, &key)
} else {
impls.find_param_trait_impl(&trait_ref.name, &trait_arg_key(&trait_ref.args), &key)
}
}
#[must_use]
pub fn check_supertrait_obligations(trait_ref: &TraitRef, ty: &Type, impls: &ImplTable) -> bool {
let key = type_key(ty);
for supertrait in impls.all_supertraits(&trait_ref.name) {
if impls.find_trait_impl(&supertrait, &key).is_none() {
return false;
}
}
true
}
#[must_use]
pub fn resolve_method(receiver: &Type, method: &str, impls: &ImplTable) -> Option<ResolvedMethod> {
let key = type_key(receiver);
if let Some(impl_id) = impls.find_inherent_impl(&key) {
if let Some(entry) = impls.get_entry(impl_id) {
if entry.methods.iter().any(|m| m == method) {
return Some(ResolvedMethod {
impl_id,
trait_ref: None,
method: method.to_owned(),
});
}
}
}
for entry in impls.entries() {
if entry.type_key == key
&& entry.trait_ref.is_some()
&& entry.methods.iter().any(|m| m == method)
{
return Some(ResolvedMethod {
impl_id: entry.id,
trait_ref: entry.trait_ref.clone(),
method: method.to_owned(),
});
}
}
None
}
#[must_use]
pub fn type_key(ty: &Type) -> String {
match ty {
Type::Primitive(p) => format!("{p:?}"),
Type::Named(n) => n.name.clone(),
Type::Generic(g) => {
let args = g.args.iter().map(type_key).collect::<Vec<_>>().join(", ");
format!("{}[{}]", g.constructor, args)
}
Type::Tuple(elems) => {
let elems = elems.iter().map(type_key).collect::<Vec<_>>().join(", ");
format!("({})", elems)
}
Type::Function(f) => {
let params = f.params.iter().map(type_key).collect::<Vec<_>>().join(", ");
format!("Fn({}) -> {}", params, type_key(&f.ret))
}
Type::Optional(inner) => format!("{}?", type_key(inner)),
Type::Result(ok, err) => format!("Result[{}, {}]", type_key(ok), type_key(err)),
Type::TypeVar(id) => format!("?{id}"),
Type::Refined(base, _) => type_key(base),
Type::Flexible(_) => "Flexible".to_string(),
Type::Error => "Error".to_string(),
}
}
#[must_use]
pub fn trait_arg_key(args: &[Type]) -> String {
args.iter().map(type_key).collect::<Vec<_>>().join(", ")
}
fn method_sig_key(method: &AIRNode) -> String {
let NodeKind::FnDecl {
params,
return_type,
..
} = &method.kind
else {
return String::new();
};
let param_keys: Vec<String> = params
.iter()
.map(|p| match &p.kind {
NodeKind::Param {
pattern, ty: None, ..
} => {
if let NodeKind::BindPat { name, .. } = &pattern.kind {
format!("@{}", name.name)
} else {
"@_".to_owned()
}
}
NodeKind::Param {
ty: Some(ty_node), ..
} => type_key_from_node(ty_node),
_ => "?".to_owned(),
})
.collect();
let ret_key = return_type
.as_deref()
.map_or_else(|| "Void".to_owned(), type_key_from_node);
format!("({}) -> {}", param_keys.join(", "), ret_key)
}
fn trait_name_from_path(path: &TypePath) -> String {
path.segments
.iter()
.map(|s| s.name.as_str())
.collect::<Vec<_>>()
.join(".")
}
fn type_key_from_node(node: &AIRNode) -> String {
match &node.kind {
NodeKind::TypeNamed { path, args } => {
let name = path
.segments
.iter()
.map(|s| s.name.as_str())
.collect::<Vec<_>>()
.join(".");
if args.is_empty() {
name
} else {
let arg_keys: Vec<_> = args.iter().map(type_key_from_node).collect();
format!("{}[{}]", name, arg_keys.join(", "))
}
}
NodeKind::TypeTuple { elems } => {
let elem_keys: Vec<_> = elems.iter().map(type_key_from_node).collect();
format!("({})", elem_keys.join(", "))
}
NodeKind::TypeOptional { inner } => format!("{}?", type_key_from_node(inner)),
NodeKind::TypeFunction { params, ret, .. } => {
let param_keys: Vec<_> = params.iter().map(type_key_from_node).collect();
format!(
"Fn({}) -> {}",
param_keys.join(", "),
type_key_from_node(ret)
)
}
NodeKind::TypeSelf => "Self".to_string(),
_ => "Unknown".to_string(),
}
}
fn type_from_node(node: &AIRNode) -> Type {
match &node.kind {
NodeKind::TypeNamed { path, args } => {
let name = path
.segments
.iter()
.map(|s| s.name.as_str())
.collect::<Vec<_>>()
.join(".");
if args.is_empty() {
match name.as_str() {
"Int" => Type::Primitive(PrimitiveType::Int),
"Float" => Type::Primitive(PrimitiveType::Float),
"Bool" => Type::Primitive(PrimitiveType::Bool),
"String" => Type::Primitive(PrimitiveType::String),
"Char" => Type::Primitive(PrimitiveType::Char),
"Void" => Type::Primitive(PrimitiveType::Void),
"Never" => Type::Primitive(PrimitiveType::Never),
_ => Type::Named(NamedType { name }),
}
} else {
let type_args: Vec<_> = args.iter().map(type_from_node).collect();
Type::Generic(GenericType {
constructor: name,
args: type_args,
})
}
}
NodeKind::TypeOptional { inner } => Type::Optional(Box::new(type_from_node(inner))),
NodeKind::TypeTuple { elems } => Type::Tuple(elems.iter().map(type_from_node).collect()),
NodeKind::TypeSelf => Type::Named(NamedType {
name: "Self".to_owned(),
}),
_ => Type::Error,
}
}
pub const SEALED_CORE_TRAITS: &[&str] = &["Equatable", "Comparable", "Displayable", "Hashable"];
pub const SEALED_PRIMITIVE_KEYS: &[&str] = &[
"Int", "Float", "String", "Bool", "Char", "Int8", "Int16", "Int32", "Int64", "Int128", "UInt8",
"UInt16", "UInt32", "UInt64", "Float32", "Float64",
];
const SIZED_INTS: &[PrimitiveType] = &[
PrimitiveType::Int8,
PrimitiveType::Int16,
PrimitiveType::Int32,
PrimitiveType::Int64,
PrimitiveType::Int128,
PrimitiveType::UInt8,
PrimitiveType::UInt16,
PrimitiveType::UInt32,
PrimitiveType::UInt64,
];
const SIZED_FLOATS: &[PrimitiveType] = &[PrimitiveType::Float32, PrimitiveType::Float64];
pub fn register_canonical_conformances(table: &mut ImplTable) {
table.register_supertrait("Comparable", "Equatable");
let register = |table: &mut ImplTable, trait_name: &str, prims: &[PrimitiveType]| {
for p in prims {
let ty = Type::Primitive(p.clone());
table.register_trait_impl_inner(trait_name, &[], &ty, true, false);
}
};
const EQUATABLE_BASE: &[PrimitiveType] = &[
PrimitiveType::Int,
PrimitiveType::Float,
PrimitiveType::String,
PrimitiveType::Bool,
PrimitiveType::Char,
];
const COMPARABLE_BASE: &[PrimitiveType] = &[
PrimitiveType::Int,
PrimitiveType::Float,
PrimitiveType::String,
PrimitiveType::Char,
];
const DISPLAYABLE_BASE: &[PrimitiveType] = &[
PrimitiveType::Int,
PrimitiveType::Float,
PrimitiveType::String,
PrimitiveType::Bool,
PrimitiveType::Char,
];
const HASHABLE_BASE: &[PrimitiveType] = &[
PrimitiveType::Int,
PrimitiveType::String,
PrimitiveType::Bool,
PrimitiveType::Char,
];
register(table, "Equatable", EQUATABLE_BASE);
register(table, "Equatable", SIZED_INTS);
register(table, "Equatable", SIZED_FLOATS);
register(table, "Comparable", COMPARABLE_BASE);
register(table, "Comparable", SIZED_INTS);
register(table, "Comparable", SIZED_FLOATS);
register(table, "Displayable", DISPLAYABLE_BASE);
register(table, "Displayable", SIZED_INTS);
register(table, "Displayable", SIZED_FLOATS);
register(table, "Hashable", HASHABLE_BASE);
register(table, "Hashable", SIZED_INTS);
}
pub fn register_canonical_conversions(table: &mut ImplTable) {
let from = |table: &mut ImplTable, source: PrimitiveType, target: PrimitiveType| {
let source_ty = Type::Primitive(source);
let target_ty = Type::Primitive(target);
table.register_param_trait_impl(
"From",
std::slice::from_ref(&source_ty),
&target_ty,
false,
);
table.register_param_trait_impl("Into", std::slice::from_ref(&target_ty), &source_ty, true);
};
from(table, PrimitiveType::Int, PrimitiveType::Float);
from(table, PrimitiveType::Float32, PrimitiveType::Float);
from(table, PrimitiveType::Char, PrimitiveType::String);
const SIGNED_WIDENING: &[PrimitiveType] = &[
PrimitiveType::Int8,
PrimitiveType::Int16,
PrimitiveType::Int32,
PrimitiveType::Int64,
PrimitiveType::Int128,
];
for (i, narrow) in SIGNED_WIDENING.iter().enumerate() {
for wide in &SIGNED_WIDENING[i + 1..] {
from(table, narrow.clone(), wide.clone());
}
from(table, narrow.clone(), PrimitiveType::Int);
}
let string_ty = Type::Primitive(PrimitiveType::String);
table.register_param_trait_impl(
"TryFrom",
std::slice::from_ref(&string_ty),
&Type::Primitive(PrimitiveType::Int),
false,
);
table.register_param_trait_impl(
"TryFrom",
std::slice::from_ref(&string_ty),
&Type::Primitive(PrimitiveType::Float),
false,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{NamedType, PrimitiveType, Type};
fn named(name: &str) -> Type {
Type::Named(NamedType {
name: name.to_owned(),
})
}
fn int() -> Type {
Type::Primitive(PrimitiveType::Int)
}
fn bool_ty() -> Type {
Type::Primitive(PrimitiveType::Bool)
}
fn dummy_span() -> bock_errors::Span {
use bock_errors::{FileId, Span};
Span {
file: FileId(0),
start: 0,
end: 0,
}
}
fn make_air_node(kind: NodeKind) -> AIRNode {
AIRNode::new(0, dummy_span(), kind)
}
fn make_module(items: Vec<AIRNode>) -> AIRNode {
make_air_node(NodeKind::Module {
path: None,
annotations: vec![],
imports: vec![],
items,
})
}
fn make_type_named(name: &str) -> AIRNode {
use bock_ast::{Ident, TypePath};
let ident = Ident {
name: name.to_owned(),
span: dummy_span(),
};
make_air_node(NodeKind::TypeNamed {
path: TypePath {
segments: vec![ident],
span: dummy_span(),
},
args: vec![],
})
}
fn make_fn_decl(name: &str) -> AIRNode {
make_fn_decl_ret(name, None)
}
fn make_fn_decl_ret(name: &str, ret: Option<&str>) -> AIRNode {
use bock_ast::{Ident, Visibility};
let body = make_air_node(NodeKind::Block {
stmts: vec![],
tail: None,
});
make_air_node(NodeKind::FnDecl {
annotations: vec![],
visibility: Visibility::Private,
is_async: false,
name: Ident {
name: name.to_owned(),
span: dummy_span(),
},
generic_params: vec![],
params: vec![],
return_type: ret.map(|r| Box::new(make_type_named(r))),
effect_clause: vec![],
where_clause: vec![],
body: Box::new(body),
})
}
fn make_class_decl(name: &str, methods: Vec<AIRNode>) -> AIRNode {
use bock_ast::{Ident, Visibility};
make_air_node(NodeKind::ClassDecl {
annotations: vec![],
visibility: Visibility::Private,
name: Ident {
name: name.to_owned(),
span: dummy_span(),
},
generic_params: vec![],
base: None,
traits: vec![],
fields: vec![],
methods,
})
}
fn make_impl_block(
trait_name: Option<&str>,
target_name: &str,
methods: Vec<AIRNode>,
) -> AIRNode {
use bock_ast::{Ident, TypePath};
let trait_path = trait_name.map(|n| TypePath {
segments: vec![Ident {
name: n.to_owned(),
span: dummy_span(),
}],
span: dummy_span(),
});
make_air_node(NodeKind::ImplBlock {
annotations: vec![],
generic_params: vec![],
trait_path,
trait_args: vec![],
target: Box::new(make_type_named(target_name)),
where_clause: vec![],
methods,
})
}
fn make_param_impl_block(
trait_name: &str,
trait_arg_names: &[&str],
target_name: &str,
methods: Vec<AIRNode>,
) -> AIRNode {
use bock_ast::{Ident, TypePath};
let trait_path = Some(TypePath {
segments: vec![Ident {
name: trait_name.to_owned(),
span: dummy_span(),
}],
span: dummy_span(),
});
let trait_args = trait_arg_names
.iter()
.map(|n| make_type_named(n))
.collect::<Vec<_>>();
make_air_node(NodeKind::ImplBlock {
annotations: vec![],
generic_params: vec![],
trait_path,
trait_args,
target: Box::new(make_type_named(target_name)),
where_clause: vec![],
methods,
})
}
#[test]
fn type_key_primitive() {
assert_eq!(type_key(&int()), "Int");
assert_eq!(type_key(&bool_ty()), "Bool");
}
#[test]
fn type_key_named() {
assert_eq!(type_key(&named("User")), "User");
}
#[test]
fn type_key_generic() {
use crate::GenericType;
let ty = Type::Generic(GenericType {
constructor: "List".to_owned(),
args: vec![int()],
});
assert_eq!(type_key(&ty), "List[Int]");
}
#[test]
fn type_key_optional() {
assert_eq!(type_key(&Type::Optional(Box::new(int()))), "Int?");
}
#[test]
fn type_key_result() {
assert_eq!(
type_key(&Type::Result(Box::new(int()), Box::new(named("Err")))),
"Result[Int, Err]"
);
}
#[test]
fn build_empty_module() {
let module = make_module(vec![]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
assert_eq!(table.entries.len(), 0);
}
#[test]
fn build_registers_trait_impl() {
let eq_method = make_fn_decl("equals");
let impl_block = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
let id = resolve_impl(&TraitRef::new("Equatable"), &named("User"), &table);
assert!(id.is_some());
}
#[test]
fn build_registers_inherent_impl() {
let method = make_fn_decl("greet");
let impl_block = make_impl_block(None, "User", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
let result = resolve_method(&named("User"), "greet", &table);
assert!(result.is_some());
let r = result.unwrap();
assert!(r.trait_ref.is_none());
assert_eq!(r.method, "greet");
}
#[test]
fn resolve_impl_found() {
let mut table = ImplTable::new();
let id = table.alloc_id();
table.entries.insert(
id,
ImplEntry {
id,
trait_ref: Some(TraitRef::new("Printable")),
type_key: "Int".to_owned(),
methods: vec!["print".to_owned()],
is_generic: false,
is_canonical: false,
trait_args: vec![],
is_derived: false,
target_type: None,
},
);
table
.trait_impl_index
.insert(("Printable".to_owned(), "Int".to_owned()), id);
assert_eq!(
resolve_impl(&TraitRef::new("Printable"), &int(), &table),
Some(id)
);
}
#[test]
fn resolve_impl_not_found() {
let table = ImplTable::new();
assert_eq!(
resolve_impl(&TraitRef::new("Printable"), &int(), &table),
None
);
}
#[test]
fn resolve_impl_wrong_type() {
let mut table = ImplTable::new();
let id = table.alloc_id();
table.entries.insert(
id,
ImplEntry {
id,
trait_ref: Some(TraitRef::new("Printable")),
type_key: "Int".to_owned(),
methods: vec!["print".to_owned()],
is_generic: false,
is_canonical: false,
trait_args: vec![],
is_derived: false,
target_type: None,
},
);
table
.trait_impl_index
.insert(("Printable".to_owned(), "Int".to_owned()), id);
assert_eq!(
resolve_impl(&TraitRef::new("Printable"), &bool_ty(), &table),
None
);
}
#[test]
fn resolve_method_inherent() {
let method = make_fn_decl("to_string");
let impl_block = make_impl_block(None, "User", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
let r = resolve_method(&named("User"), "to_string", &table);
assert!(r.is_some());
let r = r.unwrap();
assert!(r.trait_ref.is_none());
assert_eq!(r.method, "to_string");
}
#[test]
fn resolve_method_from_trait_impl() {
let method = make_fn_decl("serialize");
let impl_block = make_impl_block(Some("Serializable"), "User", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
let r = resolve_method(&named("User"), "serialize", &table);
assert!(r.is_some());
let r = r.unwrap();
assert_eq!(
r.trait_ref.as_ref().map(|t| t.name.as_str()),
Some("Serializable")
);
assert_eq!(r.method, "serialize");
}
#[test]
fn resolve_method_not_found() {
let table = ImplTable::new();
assert!(resolve_method(&int(), "foo", &table).is_none());
}
#[test]
fn resolve_method_inherent_takes_priority_over_trait() {
let inherent_method = make_fn_decl("display");
let trait_method = make_fn_decl("display");
let inherent_impl = make_impl_block(None, "User", vec![inherent_method]);
let trait_impl = make_impl_block(Some("Display"), "User", vec![trait_method]);
let module = make_module(vec![inherent_impl, trait_impl]);
let table = ImplTable::build_from(&module);
let r = resolve_method(&named("User"), "display", &table).unwrap();
assert!(r.trait_ref.is_none());
}
#[test]
fn coherence_detects_exact_overlap() {
let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
let impl2 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
let module = make_module(vec![impl1, impl2]);
let table = ImplTable::build_from(&module);
assert!(table.diags.has_errors());
assert_eq!(table.diags.error_count(), 1);
}
#[test]
fn coherence_allows_different_types() {
let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
let impl2 = make_impl_block(Some("Equatable"), "Line", vec![make_fn_decl("equals")]);
let module = make_module(vec![impl1, impl2]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
}
#[test]
fn coherence_allows_different_traits() {
let impl1 = make_impl_block(Some("Equatable"), "Point", vec![make_fn_decl("equals")]);
let impl2 = make_impl_block(Some("Comparable"), "Point", vec![make_fn_decl("compare")]);
let module = make_module(vec![impl1, impl2]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
}
fn count_e4012(table: &ImplTable) -> usize {
table
.diags
.iter()
.filter(|d| d.code == E_DUPLICATE_METHOD)
.count()
}
#[test]
fn namespace_rejects_inherent_and_trait_same_method() {
let inherent = make_impl_block(
None,
"Button",
vec![make_fn_decl_ret("render", Some("String"))],
);
let trait_impl = make_impl_block(
Some("Component"),
"Button",
vec![make_fn_decl_ret("render", Some("String"))],
);
let module = make_module(vec![inherent, trait_impl]);
let table = ImplTable::build_from(&module);
assert!(table.diags.has_errors());
assert_eq!(count_e4012(&table), 1);
}
#[test]
fn namespace_allows_empty_trait_impl_satisfied_by_inherent() {
let inherent = make_impl_block(
None,
"Button",
vec![make_fn_decl_ret("render", Some("String"))],
);
let trait_impl = make_impl_block(Some("Component"), "Button", vec![]);
let module = make_module(vec![inherent, trait_impl]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
assert_eq!(count_e4012(&table), 0);
}
#[test]
fn namespace_allows_distinct_method_names() {
let inherent = make_impl_block(None, "Button", vec![make_fn_decl("click")]);
let trait_impl = make_impl_block(
Some("Component"),
"Button",
vec![make_fn_decl_ret("render", Some("String"))],
);
let module = make_module(vec![inherent, trait_impl]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
assert_eq!(count_e4012(&table), 0);
}
#[test]
fn namespace_rejects_conflicting_signatures_across_traits() {
let impl_a = make_impl_block(
Some("TraitA"),
"Widget",
vec![make_fn_decl_ret("foo", Some("Int"))],
);
let impl_b = make_impl_block(
Some("TraitB"),
"Widget",
vec![make_fn_decl_ret("foo", Some("String"))],
);
let module = make_module(vec![impl_a, impl_b]);
let table = ImplTable::build_from(&module);
assert!(table.diags.has_errors());
assert_eq!(count_e4012(&table), 1);
}
#[test]
fn namespace_rejects_class_body_and_trait_same_method() {
let class = make_class_decl("Button", vec![make_fn_decl_ret("render", Some("String"))]);
let trait_impl = make_impl_block(
Some("Component"),
"Button",
vec![make_fn_decl_ret("render", Some("String"))],
);
let module = make_module(vec![class, trait_impl]);
let table = ImplTable::build_from(&module);
assert!(table.diags.has_errors());
assert_eq!(count_e4012(&table), 1);
}
#[test]
fn namespace_allows_class_body_satisfying_trait() {
let class = make_class_decl("Button", vec![make_fn_decl_ret("render", Some("String"))]);
let trait_impl = make_impl_block(Some("Component"), "Button", vec![]);
let module = make_module(vec![class, trait_impl]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
assert_eq!(count_e4012(&table), 0);
}
#[test]
fn coherence_skips_generic_impls() {
use bock_ast::{GenericParam, Ident, TypePath};
let generic_param = GenericParam {
id: 0,
span: dummy_span(),
name: Ident {
name: "T".to_owned(),
span: dummy_span(),
},
bounds: vec![],
};
let impl1 = make_air_node(NodeKind::ImplBlock {
annotations: vec![],
generic_params: vec![generic_param.clone()],
trait_path: Some(TypePath {
segments: vec![Ident {
name: "Printable".to_owned(),
span: dummy_span(),
}],
span: dummy_span(),
}),
trait_args: vec![],
target: Box::new(make_type_named("T")),
where_clause: vec![],
methods: vec![],
});
let impl2 = make_air_node(NodeKind::ImplBlock {
annotations: vec![],
generic_params: vec![generic_param],
trait_path: Some(TypePath {
segments: vec![Ident {
name: "Printable".to_owned(),
span: dummy_span(),
}],
span: dummy_span(),
}),
trait_args: vec![],
target: Box::new(make_type_named("T")),
where_clause: vec![],
methods: vec![],
});
let module = make_module(vec![impl1, impl2]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
}
#[test]
fn supertrait_registration_and_lookup() {
let mut table = ImplTable::new();
table.register_supertrait("Hashable", "Equatable");
let supers = table.all_supertraits("Hashable");
assert_eq!(supers, vec!["Equatable"]);
}
#[test]
fn supertrait_transitive() {
let mut table = ImplTable::new();
table.register_supertrait("C", "B");
table.register_supertrait("B", "A");
let supers = table.all_supertraits("C");
assert_eq!(supers, vec!["B", "A"]);
}
#[test]
fn check_supertrait_obligations_satisfied() {
let mut table = ImplTable::new();
table.register_supertrait("Hashable", "Equatable");
let eq_method = make_fn_decl("equals");
let hash_method = make_fn_decl("hash");
let eq_impl = make_impl_block(Some("Equatable"), "User", vec![eq_method]);
let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
let module = make_module(vec![eq_impl, hash_impl]);
let table_built = ImplTable::build_from(&module);
let mut full_table = table_built;
full_table.register_supertrait("Hashable", "Equatable");
assert!(check_supertrait_obligations(
&TraitRef::new("Hashable"),
&named("User"),
&full_table
));
}
#[test]
fn check_supertrait_obligations_missing() {
let mut table = ImplTable::new();
table.register_supertrait("Hashable", "Equatable");
let hash_method = make_fn_decl("hash");
let hash_impl = make_impl_block(Some("Hashable"), "User", vec![hash_method]);
let module = make_module(vec![hash_impl]);
let table_built = ImplTable::build_from(&module);
let mut full_table = table_built;
full_table.register_supertrait("Hashable", "Equatable");
assert!(!check_supertrait_obligations(
&TraitRef::new("Hashable"),
&named("User"),
&full_table
));
}
#[test]
fn assoc_type_manual_registration() {
let mut table = ImplTable::new();
let impl_id = table.alloc_id();
table.register_assoc_type(impl_id, "Item", int());
assert_eq!(table.resolve_assoc_type(impl_id, "Item"), Some(&int()));
assert_eq!(table.resolve_assoc_type(impl_id, "Missing"), None);
}
#[test]
fn assoc_type_not_found_for_other_impl() {
let mut table = ImplTable::new();
let id1 = table.alloc_id();
let id2 = table.alloc_id();
table.register_assoc_type(id1, "Item", int());
assert_eq!(table.resolve_assoc_type(id2, "Item"), None);
}
#[test]
fn resolve_method_on_generic_type() {
let method = make_fn_decl("push");
let impl_block = make_impl_block(None, "List", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
let receiver = Type::Named(NamedType {
name: "List".to_owned(),
});
let r = resolve_method(&receiver, "push", &table);
assert!(r.is_some());
}
fn float() -> Type {
Type::Primitive(PrimitiveType::Float)
}
fn string() -> Type {
Type::Primitive(PrimitiveType::String)
}
fn char_ty() -> Type {
Type::Primitive(PrimitiveType::Char)
}
#[test]
fn canonical_comparable_int_is_registered() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_some());
}
#[test]
fn canonical_equatable_covers_expected_primitives() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
for ty in [int(), float(), string(), bool_ty(), char_ty()] {
assert!(
resolve_impl(&TraitRef::new("Equatable"), &ty, &table).is_some(),
"Equatable should cover {ty:?}"
);
}
}
#[test]
fn canonical_comparable_excludes_bool() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
assert!(resolve_impl(&TraitRef::new("Equatable"), &bool_ty(), &table).is_some());
assert!(resolve_impl(&TraitRef::new("Comparable"), &bool_ty(), &table).is_none());
}
#[test]
fn canonical_hashable_excludes_float() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
assert!(resolve_impl(&TraitRef::new("Equatable"), &float(), &table).is_some());
assert!(resolve_impl(&TraitRef::new("Hashable"), &float(), &table).is_none());
assert!(resolve_impl(&TraitRef::new("Hashable"), &int(), &table).is_some());
}
#[test]
fn canonical_covers_sized_numerics() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
let i32_ty = Type::Primitive(PrimitiveType::Int32);
let u64_ty = Type::Primitive(PrimitiveType::UInt64);
let f32_ty = Type::Primitive(PrimitiveType::Float32);
assert!(resolve_impl(&TraitRef::new("Comparable"), &i32_ty, &table).is_some());
assert!(resolve_impl(&TraitRef::new("Equatable"), &u64_ty, &table).is_some());
assert!(resolve_impl(&TraitRef::new("Comparable"), &f32_ty, &table).is_some());
assert!(resolve_impl(&TraitRef::new("Hashable"), &f32_ty, &table).is_none());
assert!(resolve_impl(&TraitRef::new("Hashable"), &i32_ty, &table).is_some());
}
#[test]
fn canonical_entries_are_marked_canonical() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
let id = resolve_impl(&TraitRef::new("Comparable"), &int(), &table).unwrap();
assert!(table.get_entry(id).unwrap().is_canonical);
}
#[test]
fn canonical_registers_comparable_equatable_supertrait() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
assert_eq!(table.all_supertraits("Comparable"), vec!["Equatable"]);
}
#[test]
fn user_register_trait_impl_is_not_canonical() {
let mut table = ImplTable::new();
let id = table.register_trait_impl("MyTrait", &named("User"));
assert!(!table.get_entry(id).unwrap().is_canonical);
}
#[test]
fn sealing_rejects_user_impl_core_trait_for_primitive() {
let method = make_fn_decl("compare");
let impl_block = make_impl_block(Some("Comparable"), "Int", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
assert!(table.diags.has_errors());
assert_eq!(table.diags.error_count(), 1);
let diag = table.diags.iter().next().unwrap();
assert_eq!(diag.code, E_SEALED_PRIMITIVE_IMPL);
assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_none());
assert!(diag.notes.iter().any(|n| n.contains("newtype")));
}
#[test]
fn sealing_rejects_each_sealed_core_trait_for_primitive() {
for trait_name in ["Equatable", "Comparable", "Displayable", "Hashable"] {
let impl_block = make_impl_block(Some(trait_name), "String", vec![make_fn_decl("m")]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
assert!(
table.diags.has_errors(),
"{trait_name} for String should be sealed"
);
}
}
#[test]
fn sealing_allows_user_impl_core_trait_for_newtype() {
let method = make_fn_decl("compare");
let impl_block = make_impl_block(Some("Comparable"), "MyNewtype", vec![method]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
assert!(resolve_impl(&TraitRef::new("Comparable"), &named("MyNewtype"), &table).is_some());
}
#[test]
fn sealing_allows_user_impl_noncore_trait_for_primitive() {
let impl_block = make_impl_block(Some("MyTrait"), "Int", vec![make_fn_decl("m")]);
let module = make_module(vec![impl_block]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
}
#[test]
fn canonical_registration_bypasses_sealing() {
let mut table = ImplTable::new();
register_canonical_conformances(&mut table);
assert!(!table.diags.has_errors());
assert!(resolve_impl(&TraitRef::new("Comparable"), &int(), &table).is_some());
}
#[test]
fn param_impl_distinct_args_resolve_independently() {
let from_int = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let from_str =
make_param_impl_block("From", &["String"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![from_int, from_str]);
let table = ImplTable::build_from(&module);
assert!(
!table.diags.has_errors(),
"distinct trait args must not collide"
);
let float = Type::Primitive(PrimitiveType::Float);
let id_int = resolve_impl(
&TraitRef::parameterized("From", vec![int()]),
&float,
&table,
);
let id_str = resolve_impl(
&TraitRef::parameterized("From", vec![Type::Primitive(PrimitiveType::String)]),
&float,
&table,
);
assert!(id_int.is_some(), "From[Int] for Float should resolve");
assert!(id_str.is_some(), "From[String] for Float should resolve");
assert_ne!(id_int, id_str, "the two impls must be distinct");
}
#[test]
fn param_impl_missing_arg_does_not_resolve() {
let from_int = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![from_int]);
let table = ImplTable::build_from(&module);
let float = Type::Primitive(PrimitiveType::Float);
assert!(resolve_impl(
&TraitRef::parameterized("From", vec![bool_ty()]),
&float,
&table
)
.is_none());
}
#[test]
fn param_impl_duplicate_args_collide() {
let a = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let b = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![a, b]);
let table = ImplTable::build_from(&module);
assert!(
table.diags.has_errors(),
"duplicate From[Int] for Float must be a coherence error"
);
}
#[test]
fn param_and_nonparam_indexes_are_independent() {
let bare = make_impl_block(Some("From"), "Float", vec![make_fn_decl("from")]);
let param = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![bare, param]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
let float = Type::Primitive(PrimitiveType::Float);
assert!(resolve_impl(&TraitRef::new("From"), &float, &table).is_some());
assert!(resolve_impl(
&TraitRef::parameterized("From", vec![int()]),
&float,
&table
)
.is_some());
}
#[test]
fn blanket_into_derived_from_explicit_from() {
let from = make_param_impl_block("From", &["Int"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![from]);
let table = ImplTable::build_from(&module);
assert!(!table.diags.has_errors());
let float = Type::Primitive(PrimitiveType::Float);
let into_id = resolve_impl(
&TraitRef::parameterized("Into", vec![float.clone()]),
&int(),
&table,
);
assert!(
into_id.is_some(),
"blanket Into[Float] for Int should resolve"
);
let entry = table.get_entry(into_id.unwrap()).unwrap();
assert!(
entry.is_derived,
"synthesized Into entry must be is_derived"
);
}
#[test]
fn blanket_into_does_not_clobber_explicit() {
let explicit_into =
make_param_impl_block("Into", &["Float"], "Apple", vec![make_fn_decl("into")]);
let from = make_param_impl_block("From", &["Apple"], "Float", vec![make_fn_decl("from")]);
let module = make_module(vec![explicit_into, from]);
let table = ImplTable::build_from(&module);
assert!(
!table.diags.has_errors(),
"explicit Into + blanket From must not collide"
);
let float = Type::Primitive(PrimitiveType::Float);
let apple = Type::Named(NamedType {
name: "Apple".to_owned(),
});
let into_id = resolve_impl(
&TraitRef::parameterized("Into", vec![float]),
&apple,
&table,
)
.expect("Into[Float] for Apple should resolve");
let entry = table.get_entry(into_id).unwrap();
assert!(
!entry.is_derived,
"explicit Into must win over the blanket-derived one"
);
}
#[test]
fn canonical_conversions_register_from_int_for_float() {
let mut table = ImplTable::new();
register_canonical_conversions(&mut table);
let float = Type::Primitive(PrimitiveType::Float);
assert!(resolve_impl(
&TraitRef::parameterized("From", vec![int()]),
&float,
&table
)
.is_some());
assert!(resolve_impl(
&TraitRef::parameterized("Into", vec![float]),
&int(),
&table
)
.is_some());
}
#[test]
fn canonical_conversions_widen_signed_ints() {
let mut table = ImplTable::new();
register_canonical_conversions(&mut table);
let i8 = Type::Primitive(PrimitiveType::Int8);
let i64 = Type::Primitive(PrimitiveType::Int64);
assert!(resolve_impl(
&TraitRef::parameterized("From", vec![i8.clone()]),
&i64,
&table
)
.is_some());
assert!(resolve_impl(
&TraitRef::parameterized("From", vec![i8.clone()]),
&int(),
&table
)
.is_some());
assert!(resolve_impl(&TraitRef::parameterized("From", vec![i64]), &i8, &table).is_none());
}
#[test]
fn canonical_conversions_try_from_string() {
let mut table = ImplTable::new();
register_canonical_conversions(&mut table);
let string = Type::Primitive(PrimitiveType::String);
assert!(resolve_impl(
&TraitRef::parameterized("TryFrom", vec![string.clone()]),
&int(),
&table
)
.is_some());
assert!(resolve_impl(
&TraitRef::parameterized("TryFrom", vec![string]),
&Type::Primitive(PrimitiveType::Float),
&table
)
.is_some());
}
#[test]
fn blanket_into_not_derived_for_try_from() {
let tryfrom = make_param_impl_block(
"TryFrom",
&["String"],
"Int",
vec![make_fn_decl("try_from")],
);
let module = make_module(vec![tryfrom]);
let table = ImplTable::build_from(&module);
let string = Type::Primitive(PrimitiveType::String);
assert!(
resolve_impl(
&TraitRef::parameterized("TryInto", vec![int()]),
&string,
&table
)
.is_none(),
"no TryInto should be synthesized"
);
assert!(
resolve_impl(
&TraitRef::parameterized("Into", vec![int()]),
&string,
&table
)
.is_none(),
"TryFrom must not synthesize a (lossless) Into"
);
}
}