#![allow(dead_code)]
use super::obj;
use super::objects::{ObjKey, PackageKey, TCObjects, TypeKey};
use super::selection::*;
use super::typ;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::Write;
macro_rules! lookup_on_found {
($indices:ident, $i:ident, $target:expr, $et:ident, $indirect:ident, $found:expr) => {
$indices = concat_vec($indices, $i);
if $target.is_some() || $et.multiples {
return LookupResult::Ambiguous($indices.unwrap());
}
*$target = Some($found);
$indirect = $et.indirect;
};
}
#[derive(PartialEq)]
pub enum LookupResult {
Entry(ObjKey, Vec<usize>, bool),
Ambiguous(Vec<usize>),
BadMethodReceiver,
NotFound,
}
pub struct MethodSet {
list: Vec<Selection>,
}
impl MethodSet {
pub fn new(t: &TypeKey, objs: &mut TCObjects) -> MethodSet {
let mut mset_base: HashMap<String, MethodCollision> = HashMap::new();
let (tkey, is_ptr) = try_deref(*t, objs);
if is_ptr && objs.types[tkey].try_as_interface().is_some() {
return MethodSet { list: vec![] };
}
let mut current = vec![EmbeddedType::new(tkey, None, is_ptr, false)];
let mut seen: Option<HashSet<TypeKey>> = None;
while !current.is_empty() {
let mut next = vec![];
let mut fset: HashMap<String, FieldCollision> = HashMap::new();
let mut mset: HashMap<String, MethodCollision> = HashMap::new();
for et in current.iter() {
let mut tobj = &objs.types[et.typ];
if let typ::Type::Named(detail) = tobj {
if seen.is_none() {
seen = Some(HashSet::new());
}
let seen_mut = seen.as_mut().unwrap();
if seen_mut.contains(&et.typ) {
continue;
}
seen_mut.insert(et.typ);
add_to_method_set(
&mut mset,
detail.methods(),
et.indices.as_ref().unwrap_or(&vec![]),
et.indirect,
et.multiples,
objs,
);
tobj = &objs.types[detail.underlying()];
}
match tobj {
typ::Type::Struct(detail) => {
for (i, f) in detail.fields().iter().enumerate() {
let fobj = &objs.lobjs[*f];
add_to_field_set(&mut fset, f, et.multiples, objs);
if fobj.var_embedded() {
let (tkey, is_ptr) = try_deref(fobj.typ().unwrap(), objs);
next.push(EmbeddedType::new(
tkey,
concat_vec(et.indices.clone(), i),
et.indirect || is_ptr,
et.multiples,
))
}
}
}
typ::Type::Interface(detail) => {
add_to_method_set(
&mut mset,
detail.all_methods().as_ref().unwrap(),
et.indices.as_ref().unwrap_or(&vec![]),
true,
et.multiples,
objs,
);
}
_ => {}
}
}
for (k, m) in mset.iter() {
if !mset_base.contains_key(k) {
mset_base.insert(
k.clone(),
if fset.contains_key(k) {
MethodCollision::Collision
} else {
m.clone()
},
);
}
}
for (k, f) in fset.iter() {
if *f == FieldCollision::Collision {
if !mset_base.contains_key(k) {
mset_base.insert(k.clone(), MethodCollision::Collision);
}
}
}
current = consolidate_multiples(next, objs);
}
let mut list: Vec<Selection> = mset_base
.into_iter()
.filter_map(|(_, m)| match m {
MethodCollision::Method(sel) => {
Some(sel)
}
MethodCollision::Collision => None,
})
.collect();
list.sort_by(|a, b| a.id().cmp(b.id()));
MethodSet { list: list }
}
pub fn list(&self) -> &Vec<Selection> {
&self.list
}
pub fn lookup(&self, pkgkey: &PackageKey, name: &str, objs: &TCObjects) -> Option<&Selection> {
if self.list.len() == 0 {
return None;
}
let pkg = &objs.pkgs[*pkgkey];
let id = obj::get_id(Some(pkg), name).to_string();
self.list
.binary_search_by_key(&&id, |x| x.id())
.ok()
.map(|i| &self.list[i])
}
pub fn fmt(&self, f: &mut fmt::Formatter<'_>, objs: &TCObjects) -> fmt::Result {
f.write_str("MethodSet {")?;
for field in self.list.iter() {
field.fmt(f, objs)?;
}
f.write_char('}')
}
}
pub fn lookup_field_or_method(
tkey: TypeKey,
addressable: bool,
pkg: Option<PackageKey>,
name: &str,
objs: &TCObjects,
) -> LookupResult {
if let Some(named) = &objs.types[tkey].try_as_named() {
let pkey = named.underlying();
if objs.types[pkey].try_as_pointer().is_some() {
let re = lookup_field_or_method_impl(pkey, false, pkg, name, objs);
if let LookupResult::Entry(okey, _, _) = &re {
if objs.lobjs[*okey].entity_type().is_func() {
return LookupResult::NotFound;
}
}
return re;
}
}
lookup_field_or_method_impl(tkey, addressable, pkg, name, objs)
}
pub fn assertable_to(iface: TypeKey, t: TypeKey, objs: &TCObjects) -> Option<(ObjKey, bool)> {
let strict = true; if !strict && objs.types[t].is_interface(objs) {
return None;
}
missing_method(t, iface, false, objs)
}
pub fn try_deref(t: TypeKey, objs: &TCObjects) -> (TypeKey, bool) {
match &objs.types[t] {
typ::Type::Pointer(detail) => (detail.base(), true),
_ => (t, false),
}
}
pub fn deref_struct_ptr(t: TypeKey, objs: &TCObjects) -> TypeKey {
let ut = typ::underlying_type(t, objs);
match &objs.types[ut] {
typ::Type::Pointer(detail) => {
let but = typ::underlying_type(detail.base(), objs);
match &objs.types[but] {
typ::Type::Struct(_) => but,
_ => t,
}
}
_ => t,
}
}
pub fn field_index(
fields: &Vec<ObjKey>,
pkg: Option<PackageKey>,
name: &str,
objs: &TCObjects,
) -> Option<usize> {
if name != "_" {
fields
.iter()
.enumerate()
.find(|(_i, x)| objs.lobjs[**x].same_id(pkg, name, objs))
.map(|(i, _x)| i)
} else {
None
}
}
pub fn lookup_method<'a>(
methods: &'a Vec<ObjKey>,
pkg: Option<PackageKey>,
name: &str,
objs: &TCObjects,
) -> Option<(usize, &'a ObjKey)> {
if name != "_" {
methods
.iter()
.enumerate()
.find(|(_i, x)| objs.lobjs[**x].same_id(pkg, name, objs))
} else {
None
}
}
pub fn missing_method(
t: TypeKey,
intf: TypeKey,
static_: bool,
objs: &TCObjects,
) -> Option<(ObjKey, bool)> {
let ival = &objs.types[intf].try_as_interface().unwrap();
if ival.is_empty() {
return None;
}
let tval = objs.types[t].underlying_val(objs);
if let Some(detail) = tval.try_as_interface() {
for fkey in ival.all_methods().as_ref().unwrap().iter() {
let fval = &objs.lobjs[*fkey];
if let Some((_i, f)) = lookup_method(
detail.all_methods().as_ref().unwrap(),
fval.pkg(),
fval.name(),
objs,
) {
if !typ::identical_option(fval.typ(), objs.lobjs[*f].typ(), objs) {
return Some((*fkey, true));
}
} else if static_ {
return Some((*fkey, false));
}
}
return None;
}
for fkey in ival.all_methods().as_ref().unwrap().iter() {
let fval = &objs.lobjs[*fkey];
match lookup_field_or_method(t, false, fval.pkg(), fval.name(), objs) {
LookupResult::Entry(okey, _, _) => {
let result_obj = &objs.lobjs[okey];
if !result_obj.entity_type().is_func() {
return Some((*fkey, false));
} else if !typ::identical_option(fval.typ(), result_obj.typ(), objs) {
return Some((*fkey, true));
}
}
_ => return Some((*fkey, false)),
}
}
None
}
fn lookup_field_or_method_impl(
tkey: TypeKey,
addressable: bool,
pkg: Option<PackageKey>,
name: &str,
objs: &TCObjects,
) -> LookupResult {
if name == "_" {
return LookupResult::NotFound;
}
let (tkey, is_ptr) = try_deref(tkey, objs);
if is_ptr && typ::is_interface(tkey, objs) {
return LookupResult::NotFound;
}
let mut current = vec![EmbeddedType::new(tkey, None, is_ptr, false)];
let mut indices = None;
let mut target = None;
let mut indirect = false;
let mut seen: Option<HashSet<TypeKey>> = None;
while !current.is_empty() {
let mut next = vec![];
for et in current.iter() {
let mut tobj = &objs.types[et.typ];
if let typ::Type::Named(detail) = tobj {
if seen.is_none() {
seen = Some(HashSet::new());
}
let seen_mut = seen.as_mut().unwrap();
if seen_mut.contains(&et.typ) {
continue;
}
seen_mut.insert(et.typ);
if let Some((i, &okey)) = lookup_method(detail.methods(), pkg, name, objs) {
lookup_on_found!(indices, i, &mut target, et, indirect, okey);
continue; }
tobj = &objs.types[detail.underlying()];
}
match tobj {
typ::Type::Struct(detail) => {
for (i, &f) in detail.fields().iter().enumerate() {
let fobj = &objs.lobjs[f];
if fobj.same_id(pkg, name, objs) {
lookup_on_found!(indices, i, &mut target, et, indirect, f);
continue; }
if target.is_none() && fobj.var_embedded() {
let (tkey, is_ptr) = try_deref(fobj.typ().unwrap(), objs);
match &objs.types[tkey] {
typ::Type::Named(_)
| typ::Type::Struct(_)
| typ::Type::Interface(_) => next.push(EmbeddedType::new(
tkey,
concat_vec(indices.clone(), i),
et.indirect || is_ptr,
et.multiples,
)),
_ => {}
}
}
}
}
typ::Type::Interface(detail) => {
let all = detail.all_methods();
if let Some((i, &okey)) = lookup_method(all.as_ref().unwrap(), pkg, name, objs)
{
lookup_on_found!(indices, i, &mut target, et, indirect, okey);
}
}
_ => {}
}
}
if let Some(okey) = target {
let lobj = &objs.lobjs[okey];
if lobj.entity_type().is_func() && ptr_recv(lobj, objs) && !indirect && !addressable {
return LookupResult::BadMethodReceiver;
}
return LookupResult::Entry(okey, indices.unwrap(), indirect);
}
current = consolidate_multiples(next, objs);
}
LookupResult::NotFound
}
fn ptr_recv(lo: &obj::LangObj, objs: &TCObjects) -> bool {
if lo.typ().is_none() {
return false;
}
if let Some(sig) = objs.types[lo.typ().unwrap()].try_as_signature() {
if let Some(re) = sig.recv() {
let t = objs.lobjs[*re].typ().unwrap();
let (_, is_ptr) = try_deref(t, objs);
return is_ptr;
}
}
lo.entity_type().func_has_ptr_recv()
}
fn concat_vec(list: Option<Vec<usize>>, i: usize) -> Option<Vec<usize>> {
if list.is_none() {
return Some(vec![i]);
}
let mut result = list.unwrap();
result.push(i);
Some(result)
}
#[derive(Debug)]
struct EmbeddedType {
typ: TypeKey,
indices: Option<Vec<usize>>, indirect: bool,
multiples: bool,
}
impl EmbeddedType {
fn new(
typ: TypeKey,
indices: Option<Vec<usize>>,
indirect: bool,
multiples: bool,
) -> EmbeddedType {
EmbeddedType {
typ: typ,
indices: indices,
indirect: indirect,
multiples: multiples,
}
}
}
fn consolidate_multiples(list: Vec<EmbeddedType>, objs: &TCObjects) -> Vec<EmbeddedType> {
let mut result = Vec::with_capacity(list.len());
if list.len() == 0 {
return result;
}
let lookup = |map: &HashMap<TypeKey, usize>, typ: TypeKey| {
if let Some(i) = map.get(&typ) {
Some(*i)
} else {
map.iter()
.find(|(k, _i)| typ::identical(**k, typ, objs))
.map(|(_k, i)| *i)
}
};
let mut map = HashMap::new();
for et in list.into_iter() {
if let Some(i) = lookup(&map, et.typ) {
result[i].multiples = true;
} else {
map.insert(et.typ, result.len());
result.push(et);
}
}
result
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum FieldCollision {
Var(ObjKey),
Collision,
}
fn add_to_field_set(
set: &mut HashMap<String, FieldCollision>,
f: &ObjKey,
multiples: bool,
objs: &TCObjects,
) {
let key = objs.lobjs[*f].id(objs);
if !multiples {
if set
.insert(key.to_string(), FieldCollision::Var(*f))
.is_none()
{
return;
}
}
set.insert(key.to_string(), FieldCollision::Collision);
}
#[derive(Clone, Debug)]
enum MethodCollision {
Method(Selection),
Collision,
}
fn add_to_method_set(
set: &mut HashMap<String, MethodCollision>,
list: &Vec<ObjKey>,
indices: &Vec<usize>,
indirect: bool,
multiples: bool,
objs: &TCObjects,
) {
for (i, okey) in list.iter().enumerate() {
let mobj = &objs.lobjs[*okey];
let key = mobj.id(objs).to_string();
if !multiples {
if !set.contains_key(&key) && (indirect || !ptr_recv(mobj, objs)) {
set.insert(
key,
MethodCollision::Method(Selection::new(
SelectionKind::MethodVal,
None,
*okey,
concat_vec(Some(indices.clone()), i).unwrap(),
indirect,
objs,
)),
);
continue;
}
}
set.insert(key, MethodCollision::Collision);
}
}