use llmcc_core::context::CompileUnit;
use llmcc_core::ir::{HirKind, HirNode};
use llmcc_core::symbol::{SYM_KIND_TYPES, SymKind, SymKindSet, Symbol};
use llmcc_resolver::BinderScopes;
use crate::token::LangRust;
#[tracing::instrument(skip_all)]
pub fn infer_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
match node.kind_id() {
LangRust::boolean_literal => get_primitive_type(scopes, "bool"),
LangRust::integer_literal => get_primitive_type(scopes, "i32"),
LangRust::float_literal => get_primitive_type(scopes, "f64"),
LangRust::char_literal => get_primitive_type(scopes, "char"),
LangRust::string_literal => get_primitive_type(scopes, "str"),
LangRust::primitive_type | LangRust::type_identifier => {
let ident = node.find_ident(unit)?;
if let Some(sym) = ident.opt_symbol() {
if sym.kind().is_defined_type() {
return Some(sym);
}
if sym.kind() != SymKind::UnresolvedType {
return Some(sym);
}
}
if let Some(sym) = scopes.lookup_symbol(ident.name, SYM_KIND_TYPES) {
return Some(sym);
}
ident.opt_symbol()
}
LangRust::block => infer_block(unit, scopes, node),
LangRust::generic_type_with_turbofish => node
.child_by_field(unit, LangRust::field_type)
.and_then(|ty_node| infer_type(unit, scopes, &ty_node)),
LangRust::generic_type => infer_generic_type(unit, scopes, node),
LangRust::array_expression => infer_array_expression(unit, scopes, node),
LangRust::range_expression => infer_range_expression(unit, scopes, node),
LangRust::tuple_expression => infer_tuple_expression(unit, scopes, node),
LangRust::struct_expression => infer_struct_expression(unit, scopes, node),
LangRust::field_expression => infer_field_expression(unit, scopes, node),
LangRust::scoped_identifier => infer_scoped_identifier(unit, scopes, node),
LangRust::scoped_type_identifier => infer_scoped_identifier(unit, scopes, node),
LangRust::identifier => {
let ident = node.find_ident(unit)?;
let symbol = ident.opt_symbol()?;
if let Some(type_id) = symbol.type_of() {
unit.opt_get_symbol(type_id)
} else {
Some(symbol)
}
}
LangRust::call_expression | LangRust::field_identifier => node
.child_by_field(unit, LangRust::field_function)
.and_then(|func_node| infer_type(unit, scopes, &func_node))
.and_then(|sym| {
if sym.kind() == SymKind::Function
&& let Some(ret_id) = sym.type_of()
{
return unit.opt_get_symbol(ret_id);
}
Some(sym)
}),
LangRust::index_expression => infer_index_expression(unit, scopes, node),
LangRust::binary_expression => infer_binary_expression(unit, scopes, node),
LangRust::reference_expression => {
infer_from_children(unit, scopes, node, &[]).and_then(|sym| {
if let Some(type_id) = sym.type_of() {
unit.opt_get_symbol(type_id)
} else {
Some(sym)
}
})
}
LangRust::unary_expression => node
.child_by_field(unit, LangRust::field_argument)
.and_then(|arg_node| infer_type(unit, scopes, &arg_node))
.or_else(|| infer_from_children(unit, scopes, node, &[])),
LangRust::expression_statement => infer_from_children(unit, scopes, node, &[]),
LangRust::type_cast_expression => node
.child_by_field(unit, LangRust::field_type)
.and_then(|ty_node| infer_type(unit, scopes, &ty_node)),
LangRust::if_expression => infer_if_expression(unit, scopes, node),
LangRust::array_type => infer_array_type(unit, scopes, node),
LangRust::tuple_type => infer_tuple_type(unit, scopes, node),
LangRust::function_type => infer_function_type(unit, scopes, node),
LangRust::reference_type => infer_reference_type(unit, scopes, node),
LangRust::pointer_type => infer_pointer_type(unit, scopes, node),
LangRust::abstract_type => infer_abstract_type(unit, scopes, node),
LangRust::bounded_type => infer_bounded_type(unit, scopes, node),
LangRust::trait_bounds => infer_trait_bounds(unit, scopes, node),
_ => {
if let Some(ident) = node.find_ident(unit) {
return ident.opt_symbol();
}
None
}
}
}
#[tracing::instrument(skip_all)]
fn get_primitive_type<'tcx>(scopes: &BinderScopes<'tcx>, name: &str) -> Option<&'tcx Symbol> {
scopes
.lookup_globals(name, SymKindSet::from_kind(SymKind::Primitive))?
.last()
.copied()
}
fn infer_block<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
for child in node.children(unit).into_iter().rev() {
if is_syntactic_noise(unit, &child) {
continue;
}
let kind_id = child.kind_id();
if kind_id == LangRust::let_declaration {
continue;
}
if kind_id == LangRust::expression_statement {
if let Some(sym) = infer_from_children(unit, scopes, &child, &[]) {
return Some(sym);
}
continue;
}
if let Some(sym) = infer_type(unit, scopes, &child) {
return Some(sym);
}
}
None
}
fn infer_array_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
infer_from_children(unit, scopes, node, &[LangRust::field_length])
}
fn infer_range_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
infer_from_children(unit, scopes, node, &[]).or_else(|| get_primitive_type(scopes, "i32"))
}
fn infer_tuple_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let children = node.children(unit);
let mut _elem_types = Vec::new();
for child in children {
if !child.is_trivia()
&& let Some(elem_type) = infer_type(unit, scopes, &child)
{
_elem_types.push(elem_type);
}
}
None
}
fn infer_struct_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
if let Some(name_node) = node.child_by_field(unit, LangRust::field_name)
&& let Some(sym) = infer_type(unit, scopes, &name_node)
&& SYM_KIND_TYPES.contains(sym.kind())
{
tracing::trace!("inferring struct type from name node");
return Some(sym);
}
None
}
#[tracing::instrument(skip_all)]
fn infer_field_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let value_node = node.child_by_field(unit, LangRust::field_value)?;
let obj_type = infer_type(unit, scopes, &value_node)?;
let field_node = node.child_by_field(unit, LangRust::field_field)?;
let field_ident = field_node.find_ident(unit)?;
if field_node.kind() == HirKind::Text {
let field_text = unit.hir_text(&field_node);
if let Ok(index) = field_text.parse::<usize>() {
if let Some(nested) = obj_type.nested_types()
&& let Some(elem_id) = nested.get(index)
{
return unit.opt_get_symbol(*elem_id);
}
return None;
}
}
scopes
.lookup_member_symbol(obj_type, field_ident.name, Some(SymKind::Field))
.and_then(|field_sym| {
if let Some(type_id) = field_sym.type_of() {
unit.opt_get_symbol(type_id)
} else {
Some(field_sym)
}
})
}
#[tracing::instrument(skip_all)]
fn infer_scoped_identifier<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let idents = node.collect_idents(unit);
if idents.is_empty() {
return None;
}
let qualified_names: Vec<&str> = idents.iter().map(|i| i.name).collect();
tracing::trace!("resolving scoped ident {:?}", qualified_names);
let resolved_path: Vec<&str>;
let crate_name_owned = unit.unit_meta().package_name.clone();
let lookup_path = if !qualified_names.is_empty() && qualified_names[0] == "crate" {
if let Some(ref crate_name) = crate_name_owned {
resolved_path = std::iter::once(crate_name.as_str())
.chain(qualified_names[1..].iter().copied())
.collect();
&resolved_path[..]
} else {
&qualified_names[..]
}
} else {
&qualified_names[..]
};
scopes.lookup_qualified_symbol(lookup_path, SymKindSet::empty())
}
fn infer_index_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let value_node = node.child_by_field(unit, LangRust::field_value)?;
let obj_type = infer_type(unit, scopes, &value_node)?;
if let Some(nested) = obj_type.nested_types()
&& let Some(elem_id) = nested.first()
{
return unit.opt_get_symbol(*elem_id);
}
None
}
#[tracing::instrument(skip_all)]
fn infer_binary_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let children = node.children(unit);
let left_node = children.first()?;
let operator = node.child_by_field(unit, LangRust::field_operator)?;
match operator.kind_id() {
LangRust::Text_EQEQ
| LangRust::Text_NE
| LangRust::Text_LT
| LangRust::Text_GT
| LangRust::Text_LE
| LangRust::Text_GE
| LangRust::Text_AMPAMP
| LangRust::Text_PIPEPIPE => get_primitive_type(scopes, "bool"),
LangRust::Text_PLUS
| LangRust::Text_MINUS
| LangRust::Text_STAR
| LangRust::Text_SLASH
| LangRust::Text_PERCENT => infer_type(unit, scopes, left_node),
_ => None,
}
}
fn infer_if_expression<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
if let Some(consequence) = node.child_by_field(unit, LangRust::field_consequence) {
return infer_block(unit, scopes, &consequence);
}
None
}
fn infer_array_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
if let Some(sn) = node.as_scope()
&& let Some(array_ident) = sn.opt_ident()
&& let Some(array_symbol) = scopes.lookup_symbol(
array_ident.name,
SymKindSet::from_kind(SymKind::CompositeType),
)
{
return Some(array_symbol);
}
let elem_node = node.child_by_field(unit, LangRust::field_element)?;
infer_type(unit, scopes, &elem_node)
}
fn infer_tuple_type<'tcx>(
_unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
if let Some(sn) = node.as_scope()
&& let Some(tuple_ident) = sn.opt_ident()
&& let Some(tuple_symbol) = scopes.lookup_symbol(
tuple_ident.name,
SymKindSet::from_kind(SymKind::CompositeType),
)
{
return Some(tuple_symbol);
}
None
}
fn infer_function_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
if let Some(trait_node) = node.child_by_field(unit, LangRust::field_trait) {
return infer_type(unit, scopes, &trait_node);
}
if let Some(ret_node) = node.child_by_field(unit, LangRust::field_return_type)
&& let Some(ret_sym) = infer_type(unit, scopes, &ret_node)
{
return Some(ret_sym);
}
if let Some(params_node) = node.child_by_field(unit, LangRust::field_parameters) {
for child in params_node.children(unit) {
if child.is_trivia() {
continue;
}
if let Some(param_type) = infer_type(unit, scopes, &child) {
return Some(param_type);
}
}
}
None
}
fn infer_generic_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let type_node = node.child_by_field(unit, LangRust::field_type)?;
let outer_type = infer_type(unit, scopes, &type_node);
if let Some(outer) = outer_type
&& outer.kind().is_defined_type()
{
return Some(outer);
}
if let Some(type_args) = node.child_by_field(unit, LangRust::field_type_arguments) {
for child in type_args.children(unit) {
if child.is_trivia() || child.kind_id() == LangRust::lifetime {
continue;
}
if let Some(inner_type) = infer_type(unit, scopes, &child) {
if inner_type.kind().is_defined_type() {
return Some(inner_type);
}
}
}
}
outer_type
}
fn infer_reference_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
node.child_by_field(unit, LangRust::field_type)
.and_then(|type_node| infer_type(unit, scopes, &type_node))
}
fn infer_pointer_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
node.child_by_field(unit, LangRust::field_type)
.and_then(|type_node| infer_type(unit, scopes, &type_node))
}
fn infer_abstract_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
let trait_node = node.child_by_field(unit, LangRust::field_trait)?;
infer_type(unit, scopes, &trait_node)
}
fn infer_bounded_type<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
for child in node.children(unit) {
if child.kind_id() == LangRust::lifetime {
continue;
}
if child.is_trivia() {
continue;
}
if let Some(sym) = infer_type(unit, scopes, &child) {
return Some(sym);
}
}
None
}
fn infer_trait_bounds<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
) -> Option<&'tcx Symbol> {
for child in node.children(unit) {
if child.kind_id() == LangRust::lifetime || child.is_trivia() {
continue;
}
if let Some(sym) = infer_type(unit, scopes, &child) {
return Some(sym);
}
}
None
}
fn is_syntactic_noise<'tcx>(unit: &CompileUnit<'tcx>, node: &HirNode<'tcx>) -> bool {
if node.is_trivia() {
return true;
}
if node.child_count() > 0 {
return false;
}
unit.hir_text(node)
.chars()
.all(|ch| ch.is_whitespace() || ch.is_ascii_punctuation())
}
fn infer_from_children<'tcx>(
unit: &CompileUnit<'tcx>,
scopes: &BinderScopes<'tcx>,
node: &HirNode<'tcx>,
skip_fields: &[u16],
) -> Option<&'tcx Symbol> {
for child in node.children(unit) {
if skip_fields.contains(&child.field_id()) {
continue;
}
if is_syntactic_noise(unit, &child) {
continue;
}
if let Some(sym) = infer_type(unit, scopes, &child) {
return Some(sym);
}
}
None
}