use std::sync::Arc;
use brink_format::DefinitionId;
use brink_ir::hir::{Expr, HirFile};
use brink_ir::{FileId, HostManifest, ParamInfo, SymbolIndex, SymbolKind};
use crate::annotations::resolve as resolve_annotation;
use crate::external_check::{InferredType, infer_literal_type};
use crate::infer::Ty;
use crate::resolve::{BareItemResult, lookup_by_name, lookup_list_item_bare};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sig {
pub name: String,
pub kind: SymbolKind,
pub params: Vec<ParamInfo>,
pub value_type: Option<InferredType>,
pub value_ty: Option<Ty>,
pub is_local: bool,
pub param_annotations: Vec<Option<Ty>>,
pub return_annotation: Option<Ty>,
}
fn ty_to_inferred_type(ty: &Ty) -> Option<InferredType> {
match ty {
Ty::Int => Some(InferredType::Int),
Ty::Float => Some(InferredType::Float),
Ty::Bool => Some(InferredType::Bool),
Ty::String => Some(InferredType::String),
Ty::Divert => Some(InferredType::Divert),
Ty::List(name) => Some(InferredType::List(name.clone())),
Ty::Weighted(_)
| Ty::Tower(_)
| Ty::Array(_)
| Ty::Map(_, _)
| Ty::Struct(_)
| Ty::Fn(..)
| Ty::Handle(_)
| Ty::Option(_)
| Ty::Range { .. }
| Ty::Content
| Ty::Unknown
| Ty::Conflicted => None,
}
}
fn value_type_with_annotation_override(
literal_type: Option<InferredType>,
annotation: Option<&brink_ir::TypeExpr>,
names: &crate::annotations::TypeNames,
) -> Option<InferredType> {
annotation
.and_then(|ann| resolve_annotation(ann, names))
.and_then(|ty| ty_to_inferred_type(&ty))
.or(literal_type)
}
pub(crate) fn literal_ty(expr: &Expr, index: &SymbolIndex) -> Option<Ty> {
match expr {
Expr::ArrayLiteral(a) => Some(Ty::Array(Box::new(crate::infer::unify_all(
a.elements
.iter()
.map(|e| literal_ty(e, index).unwrap_or(Ty::Unknown)),
)))),
Expr::MapLiteral(m) => {
let keys = m
.entries
.iter()
.map(|(k, _)| literal_ty(k, index).unwrap_or(Ty::Unknown));
let vals: Vec<Ty> = m
.entries
.iter()
.map(|(_, v)| literal_ty(v, index).unwrap_or(Ty::Unknown))
.collect();
Some(Ty::Map(
Box::new(crate::infer::unify_all(keys)),
Box::new(crate::infer::unify_all(vals)),
))
}
Expr::StructLiteral(sl) => Some(Ty::Struct(sl.shape.text.clone())),
_ => infer_literal_type(expr, index).map(Ty::from),
}
}
fn declared_value_ty(
value: &Expr,
annotation: Option<&brink_ir::TypeExpr>,
index: &SymbolIndex,
files: &[(FileId, &HirFile)],
names: &crate::annotations::TypeNames,
manifest: Option<&HostManifest>,
native: bool,
) -> Option<Ty> {
if let Some(ty) = annotation.and_then(|ann| resolve_annotation(ann, names)) {
return Some(ty);
}
literal_ty(value, index).or_else(|| declared_fn_type(value, index, files, manifest, native))
}
fn declared_fn_type(
value: &Expr,
index: &SymbolIndex,
files: &[(FileId, &HirFile)],
manifest: Option<&HostManifest>,
native: bool,
) -> Option<Ty> {
let (target_path, bound_args) = match value {
Expr::FnLiteral(fl) => (&fl.target, fl.args.len()),
Expr::Path(path) if native => (path, 0),
_ => return None,
};
let target_name = target_path
.segments
.iter()
.map(|s| s.text.as_str())
.collect::<Vec<_>>()
.join(".");
if matches!(value, Expr::Path(_))
&& (index.by_name.get(target_name.as_str()).is_some_and(|ids| {
ids.iter().any(|id| {
index.symbols.get(id).is_some_and(|i| {
matches!(
i.kind,
SymbolKind::Variable | SymbolKind::Constant | SymbolKind::ListItem
)
})
})
}) || !matches!(
lookup_list_item_bare(index, target_name.as_str()),
BareItemResult::NotFound
))
{
return None;
}
let target_def = lookup_by_name(
index,
&crate::resolve::ImportScope::default(),
&target_name,
&[SymbolKind::Knot],
)?;
let target_info = index.symbols.get(&target_def)?;
if target_info.detail.as_deref() != Some("function") {
return None;
}
if !files.iter().any(|&(id, _)| id == target_info.file) {
return None;
}
let target_sig = signature(target_def, index, files, manifest)?;
let remaining: Vec<Ty> = target_sig
.param_annotations
.iter()
.skip(bound_args)
.map(|a| a.clone().unwrap_or(Ty::Unknown))
.collect();
let ret = target_sig.return_annotation.clone().unwrap_or(Ty::Unknown);
Some(Ty::Fn(
remaining,
Box::new(ret),
crate::infer::FnRow::of_target(target_def),
))
}
#[must_use]
#[expect(
clippy::too_many_lines,
reason = "each per-kind annotation-resolution branch below (Variable/Constant/ \
Knot/Stitch) threads the same TypeNames bundle (lists/structs/handles, \
TM-4b + T1d-2) through its own param/return/value-type resolution — \
a wide but flat shape, not a new structural concern worth splitting out"
)]
pub fn signature(
def: DefinitionId,
index: &SymbolIndex,
files: &[(FileId, &HirFile)],
manifest: Option<&HostManifest>,
) -> Option<Arc<Sig>> {
let info = index.symbols.get(&def)?;
let hir = files
.iter()
.find(|&&(id, _)| id == info.file)
.map(|&(_, hir)| hir);
let mut value_type = None;
let mut value_ty = None;
let mut is_local = false;
let mut param_annotations = Vec::new();
let mut return_annotation = None;
if let Some(hir) = hir {
let names = || crate::annotations::TypeNames::new(index, manifest);
match info.kind {
SymbolKind::Variable => {
if let Some(v) = hir.variables.iter().find(|v| v.name.text == info.name) {
is_local = v.is_local;
value_type = value_type_with_annotation_override(
infer_literal_type(&v.value, index),
v.annotation.as_ref(),
&names(),
);
value_ty = declared_value_ty(
&v.value,
v.annotation.as_ref(),
index,
files,
&names(),
manifest,
hir.native,
);
}
}
SymbolKind::Constant => {
if let Some(c) = hir.constants.iter().find(|c| c.name.text == info.name) {
value_type = value_type_with_annotation_override(
infer_literal_type(&c.value, index),
c.annotation.as_ref(),
&names(),
);
value_ty = declared_value_ty(
&c.value,
c.annotation.as_ref(),
index,
files,
&names(),
manifest,
hir.native,
);
}
}
SymbolKind::Knot => {
if let Some(k) = hir.knots.iter().find(|k| k.name.text == info.name) {
is_local = k.is_local;
let names = names();
param_annotations = k
.params
.iter()
.map(|p| {
p.annotation
.as_ref()
.and_then(|a| resolve_annotation(a, &names))
})
.collect();
return_annotation = k
.return_type
.as_ref()
.and_then(|rt| resolve_annotation(rt, &names));
}
}
SymbolKind::Stitch => {
if let Some((knot_name, stitch_name)) = info.name.split_once('.') {
if let Some(s) = hir
.knots
.iter()
.find(|k| k.name.text == knot_name)
.and_then(|k| k.stitches.iter().find(|s| s.name.text == stitch_name))
{
is_local = s.is_local;
let names = names();
param_annotations = s
.params
.iter()
.map(|p| {
p.annotation
.as_ref()
.and_then(|a| resolve_annotation(a, &names))
})
.collect();
return_annotation = s
.return_type
.as_ref()
.and_then(|rt| resolve_annotation(rt, &names));
}
} else if let Some(k) = hir.knots.iter().find(|k| k.name.text == info.name) {
is_local = k.is_local;
let names = names();
param_annotations = k
.params
.iter()
.map(|p| {
p.annotation
.as_ref()
.and_then(|a| resolve_annotation(a, &names))
})
.collect();
return_annotation = k
.return_type
.as_ref()
.and_then(|rt| resolve_annotation(rt, &names));
}
}
_ => {}
}
}
Some(Arc::new(Sig {
name: info.name.clone(),
kind: info.kind,
params: info.params.clone(),
value_type,
value_ty,
is_local,
param_annotations,
return_annotation,
}))
}
#[must_use]
pub fn local_signature(
def: DefinitionId,
manifest: &brink_ir::SymbolManifest,
index: &SymbolIndex,
host: Option<&HostManifest>,
) -> Option<Arc<Sig>> {
let local = manifest
.locals
.iter()
.find(|l| crate::manifest::local_definition_id(&l.scope, &l.name, l.kind) == def)?;
let names = crate::annotations::TypeNames::new(index, host);
let value_ty = local
.annotation
.as_ref()
.and_then(|a| resolve_annotation(a, &names));
let value_type = value_ty.as_ref().and_then(ty_to_inferred_type);
Some(Arc::new(Sig {
name: local.name.clone(),
kind: local.kind,
params: Vec::new(),
value_type,
value_ty,
is_local: false,
param_annotations: Vec::new(),
return_annotation: None,
}))
}