use llmcc_core::context::CompileUnit;
use llmcc_core::ir::{HirId, HirNode};
use tree_sitter::Node;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FnVisibility {
Private,
Public,
Crate,
Restricted(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionParameter {
pub pattern: String,
pub ty: Option<TypeExpr>,
}
#[derive(Debug, Clone)]
pub struct FunctionDescriptor {
pub hir_id: HirId,
pub name: String,
pub visibility: FnVisibility,
pub is_async: bool,
pub is_const: bool,
pub is_unsafe: bool,
pub generics: Option<String>,
pub where_clause: Option<String>,
pub parameters: Vec<FunctionParameter>,
pub return_type: Option<TypeExpr>,
pub signature: String,
pub fqn: String,
}
impl FunctionDescriptor {
pub fn from_hir<'tcx>(
unit: CompileUnit<'tcx>,
node: &HirNode<'tcx>,
fqn: String,
) -> Option<Self> {
let ts_node = match node.inner_ts_node() {
ts if ts.kind() == "function_item" => ts,
_ => return None,
};
let name_node = ts_node.child_by_field_name("name")?;
let name = node_text(unit, name_node);
let header_text = unit
.file()
.get_text(ts_node.start_byte(), name_node.start_byte());
let fn_index = header_text.rfind("fn").unwrap_or(header_text.len());
let header_clean = clean(&header_text[..fn_index]);
let visibility = FnVisibility::from_header(&header_clean);
let is_async = header_clean
.split_whitespace()
.any(|token| token == "async");
let is_const = header_clean
.split_whitespace()
.any(|token| token == "const");
let is_unsafe = header_clean
.split_whitespace()
.any(|token| token == "unsafe");
let body_start = ts_node
.child_by_field_name("body")
.map(|body| body.start_byte())
.unwrap_or_else(|| ts_node.end_byte());
let signature = clean(&unit.file().get_text(ts_node.start_byte(), body_start));
let generics = ts_node
.child_by_field_name("type_parameters")
.map(|n| node_text(unit, n));
let where_clause = ts_node
.child_by_field_name("where_clause")
.map(|n| node_text(unit, n))
.or_else(|| extract_where_clause(&signature));
let parameters = ts_node
.child_by_field_name("parameters")
.map(|n| parse_parameters(unit, n))
.unwrap_or_default();
let return_type = ts_node
.child_by_field_name("return_type")
.map(|n| parse_return_type_node(unit, n));
Some(FunctionDescriptor {
hir_id: node.hir_id(),
name,
visibility,
is_async,
is_const,
is_unsafe,
generics,
where_clause,
parameters,
return_type,
signature,
fqn,
})
}
}
fn extract_where_clause(signature: &str) -> Option<String> {
let signature = signature.trim();
let idx = signature.find("where ")?;
let clause = signature[idx..].trim();
if clause.is_empty() {
return None;
}
Some(clause.trim_end_matches(',').to_string())
}
impl FnVisibility {
pub(crate) fn from_header(header: &str) -> Self {
if let Some(index) = header.find("pub") {
let rest = &header[index..];
let compressed: String = rest.chars().filter(|c| !c.is_whitespace()).collect();
if compressed.starts_with("pub(") && compressed.ends_with(')') {
let inner = &compressed[4..compressed.len() - 1];
if inner == "crate" {
FnVisibility::Crate
} else {
FnVisibility::Restricted(inner.to_string())
}
} else {
FnVisibility::Public
}
} else {
FnVisibility::Private
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeExpr {
Path {
segments: Vec<String>,
generics: Vec<TypeExpr>,
},
Reference {
is_mut: bool,
lifetime: Option<String>,
inner: Box<TypeExpr>,
},
Tuple(Vec<TypeExpr>),
ImplTrait { bounds: String },
Unknown(String),
}
impl TypeExpr {
pub fn path_segments(&self) -> Option<&[String]> {
match self {
TypeExpr::Path { segments, .. } => Some(segments),
_ => None,
}
}
pub fn generics(&self) -> Option<&[TypeExpr]> {
match self {
TypeExpr::Path { generics, .. } => Some(generics),
_ => None,
}
}
}
fn parse_parameters<'tcx>(
unit: CompileUnit<'tcx>,
params_node: Node<'tcx>,
) -> Vec<FunctionParameter> {
let mut params = Vec::new();
let mut cursor = params_node.walk();
for child in params_node.named_children(&mut cursor) {
match child.kind() {
"parameter" => {
let pattern = child
.child_by_field_name("pattern")
.map(|n| node_text(unit, n))
.unwrap_or_else(|| node_text(unit, child));
let ty = child
.child_by_field_name("type")
.map(|n| parse_type_expr(unit, n));
params.push(FunctionParameter { pattern, ty });
}
"self_parameter" => {
params.push(FunctionParameter {
pattern: node_text(unit, child),
ty: None,
});
}
_ => {}
}
}
params
}
pub(crate) fn parse_type_expr<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> TypeExpr {
match node.kind() {
"type_identifier" | "primitive_type" => TypeExpr::Path {
segments: node_text(unit, node)
.split("::")
.map(|s| s.to_string())
.collect(),
generics: Vec::new(),
},
"scoped_type_identifier" => TypeExpr::Path {
segments: node_text(unit, node)
.split("::")
.map(|s| s.to_string())
.collect(),
generics: Vec::new(),
},
"generic_type" => parse_generic_type(unit, node),
"reference_type" => parse_reference_type(unit, node),
"tuple_type" => {
let mut types = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if is_type_node(child.kind()) {
types.push(parse_type_expr(unit, child));
}
}
TypeExpr::Tuple(types)
}
"impl_trait_type" => TypeExpr::ImplTrait {
bounds: node_text(unit, node),
},
_ => TypeExpr::Unknown(node_text(unit, node)),
}
}
fn parse_generic_type<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> TypeExpr {
let mut base_segments: Vec<String> = Vec::new();
let mut generics = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"type_identifier" | "scoped_type_identifier" => {
base_segments = node_text(unit, child)
.split("::")
.map(|s| s.to_string())
.collect();
}
"type_arguments" => {
generics = parse_type_arguments(unit, child);
}
_ => {}
}
}
if base_segments.is_empty() {
base_segments = node_text(unit, node)
.split("::")
.map(|s| s.to_string())
.collect();
}
TypeExpr::Path {
segments: base_segments,
generics,
}
}
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));
}
_ => {}
}
}
args
}
fn parse_reference_type<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> TypeExpr {
let mut lifetime = None;
let mut is_mut = false;
let mut inner = None;
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"lifetime" => lifetime = Some(node_text(unit, child)),
"mutable_specifier" => is_mut = true,
kind if is_type_node(kind) => inner = Some(parse_type_expr(unit, child)),
_ => {}
}
}
let inner = inner.unwrap_or_else(|| TypeExpr::Unknown(node_text(unit, node)));
TypeExpr::Reference {
is_mut,
lifetime,
inner: Box::new(inner),
}
}
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 parse_return_type_node<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> TypeExpr {
let mut expr_opt = None;
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if expr_opt.is_none() && is_type_node(child.kind()) {
expr_opt = Some(parse_type_expr(unit, child));
}
}
let mut expr = if let Some(expr) = expr_opt {
expr
} else if let Some(inner) = node.child_by_field_name("type") {
parse_type_expr(unit, inner)
} else {
parse_type_expr(unit, node)
};
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
if child.kind() == "type_arguments" {
let extra = parse_type_arguments(unit, child);
if !extra.is_empty() {
if let TypeExpr::Path { generics, .. } = &mut expr {
generics.extend(extra);
}
}
}
}
if let TypeExpr::Unknown(text) = &expr {
if text.starts_with("impl ") {
expr = TypeExpr::ImplTrait {
bounds: text.clone(),
};
}
}
expr
}
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 node_text<'tcx>(unit: CompileUnit<'tcx>, node: Node<'tcx>) -> String {
clean(&unit.file().get_text(node.start_byte(), node.end_byte()))
}