use std::mem;
use llmcc_core::context::CompileUnit;
use llmcc_core::ir::{HirId, HirNode};
use tree_sitter::Node;
use super::function::{parse_type_expr, TypeExpr};
#[derive(Debug, Clone)]
pub struct CallDescriptor {
pub hir_id: HirId,
pub target: CallTarget,
pub arguments: Vec<CallArgument>,
pub enclosing_function: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallTarget {
Path {
segments: Vec<String>,
generics: Vec<TypeExpr>,
},
Method {
receiver: String,
method: String,
generics: Vec<TypeExpr>,
},
Chain {
base: String,
segments: Vec<ChainSegment>,
},
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainSegment {
pub method: String,
pub generics: Vec<TypeExpr>,
pub arguments: Vec<CallArgument>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallArgument {
pub text: String,
}
impl CallDescriptor {
pub fn from_call<'tcx>(
unit: CompileUnit<'tcx>,
node: &HirNode<'tcx>,
enclosing_function: Option<String>,
) -> Self {
let ts_node = node.inner_ts_node();
let function_node = ts_node.child_by_field_name("function");
let call_generics = ts_node
.child_by_field_name("type_arguments")
.map(|n| parse_type_arguments(unit, n))
.unwrap_or_default();
let target = function_node
.and_then(|func| parse_chain(unit, func, call_generics.clone()))
.or_else(|| parse_chain(unit, ts_node, call_generics.clone()))
.map(|(base, segments)| CallTarget::Chain { base, segments })
.unwrap_or_else(|| match function_node {
Some(func) => parse_call_target(unit, func, call_generics.clone()),
None => CallTarget::Unknown(clean(&node_text(unit, ts_node))),
});
let arguments = ts_node
.child_by_field_name("arguments")
.map(|args| parse_arguments(unit, args))
.unwrap_or_default();
CallDescriptor {
hir_id: node.hir_id(),
target,
arguments,
enclosing_function,
}
}
}
fn parse_arguments<'tcx>(unit: CompileUnit<'tcx>, args_node: Node<'tcx>) -> Vec<CallArgument> {
let mut cursor = args_node.walk();
args_node
.named_children(&mut cursor)
.map(|arg| CallArgument {
text: clean(&node_text(unit, arg)),
})
.collect()
}
fn parse_call_target<'tcx>(
unit: CompileUnit<'tcx>,
node: Node<'tcx>,
call_generics: Vec<TypeExpr>,
) -> CallTarget {
match node.kind() {
"identifier" | "scoped_identifier" | "type_identifier" => {
let segments: Vec<String> = clean(&node_text(unit, node))
.split("::")
.map(|s| s.to_string())
.collect();
CallTarget::Path {
segments,
generics: call_generics,
}
}
"generic_type" => {
let base = node.child_by_field_name("type").unwrap_or(node);
let mut segments: Vec<String> = clean(&node_text(unit, base))
.split("::")
.map(|s| s.to_string())
.collect();
if segments.is_empty() {
segments.push(clean(&node_text(unit, base)));
}
let generics = node
.child_by_field_name("type_arguments")
.map(|args| parse_type_arguments(unit, args))
.unwrap_or(call_generics);
CallTarget::Path { segments, generics }
}
"generic_function" => {
let generics = node
.child_by_field_name("type_arguments")
.map(|args| parse_type_arguments(unit, args))
.unwrap_or_default();
let inner = node
.child_by_field_name("function")
.unwrap_or(node.child(0).unwrap_or(node));
let mut target = parse_call_target(unit, inner, call_generics);
match &mut target {
CallTarget::Path { generics: g, .. } => *g = generics,
CallTarget::Method { generics: g, .. } => *g = generics,
_ => {}
}
target
}
"field_expression" => {
let receiver = node
.child_by_field_name("value")
.map(|n| clean(&node_text(unit, n)))
.unwrap_or_else(|| clean(&node_text(unit, node)));
let method = node
.child_by_field_name("field")
.map(|n| clean(&node_text(unit, n)))
.unwrap_or_default();
let generics = node
.child_by_field_name("type_arguments")
.map(|n| parse_type_arguments(unit, n))
.unwrap_or(call_generics);
CallTarget::Method {
receiver,
method,
generics,
}
}
_ => {
let text = clean(&node_text(unit, node));
if let Some((receiver, method)) = parse_method_from_text(&text) {
CallTarget::Method {
receiver,
method,
generics: call_generics,
}
} else {
CallTarget::Unknown(text)
}
}
}
}
fn parse_chain<'tcx>(
unit: CompileUnit<'tcx>,
mut node: Node<'tcx>,
call_generics: Vec<TypeExpr>,
) -> Option<(String, Vec<ChainSegment>)> {
let mut segments = Vec::new();
let mut pending_generics = call_generics;
let mut pending_arguments = Vec::new();
loop {
match node.kind() {
"call_expression" => {
pending_generics = node
.child_by_field_name("type_arguments")
.map(|n| parse_type_arguments(unit, n))
.unwrap_or_default();
pending_arguments = node
.child_by_field_name("arguments")
.map(|args| parse_arguments(unit, args))
.unwrap_or_default();
let function_node = node.child_by_field_name("function")?;
node = function_node;
}
"generic_function" => {
pending_generics = node
.child_by_field_name("type_arguments")
.map(|n| parse_type_arguments(unit, n))
.unwrap_or_default();
let inner_function = node.child_by_field_name("function")?;
node = inner_function;
}
"field_expression" => {
let method = node
.child_by_field_name("field")
.map(|n| clean(&node_text(unit, n)))
.unwrap_or_default();
let generics = mem::take(&mut pending_generics);
let arguments = mem::take(&mut pending_arguments);
segments.push(ChainSegment {
method,
generics,
arguments,
});
let argument_node = node.child_by_field_name("value")?;
node = argument_node;
}
_ => {
if segments.len() < 2 {
return None;
}
let base = clean(&node_text(unit, node));
segments.reverse();
return Some((base, segments));
}
}
}
}
fn parse_type_arguments<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> Vec<TypeExpr> {
let mut args = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"type_argument" => {
if let Some(inner) = child.child_by_field_name("type") {
args.push(parse_type_expr(unit, inner));
}
}
kind if is_type_node(kind) => {
args.push(parse_type_expr(unit, child));
}
_ => {}
}
}
if args.is_empty() {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if is_type_node(child.kind()) {
args.push(parse_type_expr(unit, child));
}
}
}
args
}
fn is_type_node(kind: &str) -> bool {
matches!(
kind,
"type_identifier"
| "scoped_type_identifier"
| "generic_type"
| "reference_type"
| "tuple_type"
| "primitive_type"
| "impl_trait_type"
)
}
fn node_text<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> String {
unit.file().get_text(node.start_byte(), node.end_byte())
}
fn clean(text: &str) -> String {
let mut out = String::new();
let mut last_was_ws = false;
for ch in text.chars() {
if ch.is_whitespace() {
if !last_was_ws && !out.is_empty() {
out.push(' ');
}
last_was_ws = true;
} else {
out.push(ch);
last_was_ws = false;
}
}
out.trim().to_string()
}
fn parse_method_from_text(text: &str) -> Option<(String, String)> {
let idx = text.rfind('.')?;
let (receiver, method_part) = text.split_at(idx);
Some((
receiver.trim().to_string(),
method_part.trim_start_matches('.').to_string(),
))
}