use std::sync::Arc;
use anyhow::{Result, bail};
use syn::{Expr, Lit, Pat, UnOp};
use crate::interpreter::bytecode::StructShape;
use crate::interpreter::bytecode::{
BinKind, CapSource, DISCARD, Member, Op, PLit, PPat, PatInfo, Reg, ScalarTy, StructLit,
};
use crate::interpreter::serde_attrs::serde_rename;
use super::{
CollectTarget, Compiler, FnState, HashMap, NameLoc, Res, TypeIr, collect_pattern_names,
first_generic_type, idx16, int_literal,
};
impl Compiler<'_> {
pub(super) fn compile_args<'e>(&mut self, args: impl Iterator<Item = &'e Expr>) -> Result<Reg> {
let list: Vec<&Expr> = args.collect();
let base = self.cur().reg_top;
for _ in 0..list.len() {
self.alloc();
}
for (i, a) in list.iter().enumerate() {
self.compile_into(base + idx16(i), a)?;
}
Ok(base)
}
fn record_call_type_args(&mut self, path: &syn::Path) -> u32 {
let Some(seg) = path.segments.last() else {
return u32::MAX;
};
let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
return u32::MAX;
};
let mut types = Vec::new();
for a in &ab.args {
if let syn::GenericArgument::Type(t) = a {
types.push(self.lower_ir(t));
}
}
if types.is_empty() {
return u32::MAX;
}
let table = &mut self.cur().call_type_args;
table.push(Arc::from(types.into_boxed_slice()));
u32::try_from(table.len() - 1).expect("type-arg table exceeds u32 indices")
}
pub(super) fn compile_call(&mut self, dst: Reg, c: &syn::ExprCall) -> Result<()> {
let Expr::Path(path_expr) = &*c.func else {
let callee = self.compile_expr(&c.func)?;
let base = self.compile_args(c.args.iter())?;
self.emit(Op::CallValue {
dst,
callee,
base,
argc: idx16(c.args.len()),
});
self.emit_mut_arg_writebacks(c.args.iter(), base)?;
return Ok(());
};
let path = &path_expr.path;
if self.ctx.async_mode && is_tokio_spawn(path) {
match c.args.first() {
Some(Expr::Async(block)) if c.args.len() == 1 => {
return self.compile_spawn(dst, &block.block);
}
_ => bail!("tokio::spawn needs an async block in this interpreter"),
}
}
let coerce = self.call_coerce(c, path);
let argc = idx16(c.args.len());
if self.try_compile_closure_call(dst, c, path, argc)? {
return Ok(());
}
let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
let resolved = match self.resolve_path_res(&segs) {
Ok(r) => r,
Err(_) => Res::External(segs.clone()),
};
let path_segs = match resolved {
Res::Fn(idx) => {
let targ = self.record_call_type_args(path);
let base = self.compile_args(c.args.iter())?;
self.emit(Op::CallFn {
dst,
func: idx,
base,
argc,
targ,
});
self.emit_mut_arg_writebacks(c.args.iter(), base)?;
return Ok(());
}
Res::Struct(canon) => vec![canon.to_string()],
Res::TypeMember(canon, rest) => {
if let Some(variant) = self.enum_variant(&canon, &rest, |fields| {
matches!(fields, syn::Fields::Unnamed(fields) if fields.unnamed.len() == argc as usize)
}) {
let base = self.compile_args(c.args.iter())?;
let info = self.add_enum_variant(variant);
self.emit(Op::MakeEnum {
dst,
info,
base,
count: argc,
});
return Ok(());
}
let mut segs = vec![canon.to_string()];
segs.extend(rest);
segs
}
Res::Alias(m, target) => {
let aliased = match &*target {
syn::Type::Path(p) => self.ctx.resolver.resolve_struct_key(m, &p.path),
_ => None,
};
match aliased {
Some(canon) => vec![canon.to_string()],
None => bail!("cannot call `{}`", segs.join("::")),
}
}
Res::Enum(_) | Res::Module | Res::Const(_) => {
bail!("cannot call `{}`", segs.join("::"))
}
Res::External(segs) => {
if is_transparent_new(&segs) && c.args.len() == 1 {
return self.compile_into(dst, &c.args[0]);
}
segs
}
};
let p = self.add_path(path_segs, coerce);
let base = self.compile_args(c.args.iter())?;
self.emit(Op::CallPath {
dst,
path: p,
base,
argc,
});
Ok(())
}
fn call_coerce(&mut self, c: &syn::ExprCall, path: &syn::Path) -> Option<TypeIr> {
let coerce = path
.segments
.last()
.and_then(first_generic_type)
.map(|t| self.lower_ir(t));
match coerce {
Some(ty) => Some(ty),
None => match &self.json_let {
Some((ptr, ty)) if std::ptr::eq(*ptr, c) => {
let ty = ty.clone();
self.json_let = None;
Some(ty)
}
_ => self.json_tails.get(&std::ptr::from_ref(c)).cloned(),
},
}
}
fn try_compile_closure_call(
&mut self,
dst: Reg,
c: &syn::ExprCall,
path: &syn::Path,
argc: u16,
) -> Result<bool> {
if path.segments.len() != 1 {
return Ok(false);
}
let name = path.segments[0].ident.to_string();
let callee = match self.resolve(&name) {
NameLoc::Local(reg) => Some(reg),
NameLoc::Cell(cell) => {
let reg = self.alloc();
self.emit(Op::LoadCell { dst: reg, cell });
Some(reg)
}
NameLoc::Upvalue(idx) => {
let reg = self.alloc();
self.emit(Op::LoadUpvalue { dst: reg, idx });
Some(reg)
}
NameLoc::None => None,
};
let Some(callee) = callee else {
return Ok(false);
};
let base = self.compile_args(c.args.iter())?;
self.emit(Op::CallValue {
dst,
callee,
base,
argc,
});
self.emit_mut_arg_writebacks(c.args.iter(), base)?;
Ok(true)
}
pub(super) fn compile_method(&mut self, dst: Reg, m: &syn::ExprMethodCall) -> Result<()> {
if m.method == "copy_from_slice" {
let Expr::Index(ix) = &*m.receiver else {
bail!("copy_from_slice is only supported on a `v[a..b]` receiver");
};
let Expr::Range(r) = &*ix.index else {
bail!("copy_from_slice is only supported on a `v[a..b]` receiver");
};
let Some(src) = m.args.first() else {
bail!("copy_from_slice takes the source slice");
};
let recv = self.compile_expr(&ix.expr)?;
let base = self.cur().reg_top;
for _ in 0..3 {
self.alloc();
}
match &r.start {
Some(e) => self.compile_into(base, e)?,
None => self.emit(Op::LoadInt { dst: base, v: 0 }),
}
match &r.end {
Some(e) => {
self.compile_into(base + 1, e)?;
if matches!(r.limits, syn::RangeLimits::Closed(_)) {
self.emit(Op::BinImm {
dst: base + 1,
a: base + 1,
imm: 1,
op: BinKind::Add,
});
}
}
None => self.emit(Op::LoadInt {
dst: base + 1,
v: i64::MAX,
}),
}
self.compile_into(base + 2, src)?;
let name = self.add_name("copy_from_slice".to_string());
self.set_line(m.method.span());
self.emit(Op::Method {
dst,
recv,
name,
base,
argc: 3,
});
return Ok(());
}
if dst != DISCARD
&& m.method == "unwrap_or"
&& m.args.len() == 1
&& let Expr::MethodCall(c) = &*m.receiver
&& (c.method == "copied" || c.method == "cloned")
&& c.args.is_empty()
&& let Expr::MethodCall(g) = &*c.receiver
&& g.method == "get"
&& g.args.len() == 1
{
let recv = self.compile_expr(&g.receiver)?;
let key = self.compile_expr(&g.args[0])?;
let default = self.compile_expr(&m.args[0])?;
self.emit(Op::GetOrDefault {
dst,
recv,
key,
default,
});
return Ok(());
}
let outer_option_hint = self.option_result.take();
if m.method == "unwrap_or_default"
&& let Expr::MethodCall(inner) = &*m.receiver
&& inner.method == "unwrap_or_default"
{
self.option_result = Some(std::ptr::from_ref(inner));
}
let recv = self.compile_expr(&m.receiver)?;
self.option_result = outer_option_hint;
let base = self.compile_args(m.args.iter())?;
let (method, scalar) = self.method_name_and_scalar(m);
let name = self.add_name_with(method, scalar);
self.set_line(m.method.span());
self.emit(Op::Method {
dst,
recv,
name,
base,
argc: idx16(m.args.len()),
});
self.emit_mut_arg_writebacks(m.args.iter(), base)?;
Ok(())
}
fn method_name_and_scalar(&mut self, m: &syn::ExprMethodCall) -> (String, Option<ScalarTy>) {
let mut method = m.method.to_string();
if method == "collect" {
let from_turbofish = m.turbofish.as_ref().and_then(turbofish_collect_target);
let from_let = match self.collect_let {
Some((ptr, target)) if std::ptr::eq(ptr, m) => Some(target),
_ => None,
};
let from_tail = self.collect_tails.get(&std::ptr::from_ref(m)).copied();
if let Some(target) = from_turbofish.or(from_let).or(from_tail) {
self.collect_let = None;
method = target.method_name().to_string();
}
}
let mut scalar = turbofish_scalar(m.turbofish.as_ref());
if scalar.is_none() && m.method == "unwrap_or_default" {
let env = TyEnv {
locals: &self.typed_locals,
fn_returns: self.ctx.fn_returns,
};
scalar = option_payload(&m.receiver, &env);
}
if scalar.is_none()
&& let Some((ptr, ty)) = &self.default_let
&& std::ptr::eq(*ptr, m)
{
scalar = Some(ty.clone());
self.default_let = None;
}
if matches!(self.option_result, Some(ptr) if std::ptr::eq(ptr, m)) {
self.option_result = None;
scalar = scalar.or(Some(ScalarTy::Opt(Box::new(ScalarTy::Other))));
}
(method, scalar)
}
pub(super) fn stated_ty(&self, expr: &Expr) -> Option<ScalarTy> {
let env = TyEnv {
locals: &self.typed_locals,
fn_returns: self.ctx.fn_returns,
};
written_ty(expr, &env)
}
fn emit_mut_arg_writebacks<'e>(
&mut self,
args: impl Iterator<Item = &'e Expr>,
base: Reg,
) -> Result<()> {
for (i, arg) in args.enumerate() {
if let Expr::Reference(r) = arg
&& r.mutability.is_some()
&& let Expr::Path(p) = &*r.expr
&& p.path.segments.len() == 1
&& p.qself.is_none()
{
let name = p.path.segments[0].ident.to_string();
let location = self.resolve_for_write(&name);
self.emit_name_store(location, base + idx16(i), &name)?;
}
}
Ok(())
}
fn compile_spawn(&mut self, dst: Reg, block: &syn::Block) -> Result<()> {
self.frames.push(FnState::new("<task>".to_string()));
self.cur().num_params = 0;
let ret = self.alloc();
self.compile_block(block, ret)?;
self.emit(Op::Ret { src: ret });
let child = self.frames.pop().unwrap();
let caps: Vec<CapSource> = child.upvalues.iter().map(|(_, s)| *s).collect();
let mut chunk = child.into_chunk(self.ctx.file.clone());
chunk.module = idx16(self.ctx.module);
let parent = self.cur();
let child_idx = idx16(parent.children.len());
parent.children.push(Arc::new(chunk));
parent.child_caps.push(caps);
self.emit(Op::Spawn {
dst,
child: child_idx,
});
Ok(())
}
pub(super) fn compile_closure(&mut self, dst: Reg, c: &syn::ExprClosure) -> Result<()> {
self.frames.push(FnState::new("<closure>".to_string()));
let params: Vec<&Pat> = c.inputs.iter().collect();
self.cur().num_params = params.len();
for p in ¶ms {
let reg = self.alloc();
match p {
Pat::Ident(id) if id.subpat.is_none() => self.define(&id.ident.to_string(), reg),
_ => self.bind_pattern_irrefutable(p, reg)?,
}
}
let ret = self.alloc();
self.compile_into(ret, &c.body)?;
self.emit(Op::Ret { src: ret });
let child = self.frames.pop().unwrap();
let caps: Vec<CapSource> = child.upvalues.iter().map(|(_, s)| *s).collect();
let mut chunk = child.into_chunk(self.ctx.file.clone());
chunk.module = idx16(self.ctx.module);
let chunk = Arc::new(chunk);
let parent = self.cur();
let child_idx = idx16(parent.children.len());
parent.children.push(chunk);
parent.child_caps.push(caps);
self.emit(Op::MakeClosure {
dst,
child: child_idx,
});
Ok(())
}
pub(super) fn compile_assign(&mut self, target: &Expr, value: &Expr) -> Result<()> {
match target {
Expr::Path(p) if p.path.segments.len() == 1 => {
let name = p.path.segments[0].ident.to_string();
let location = self.resolve_for_write(&name);
let value = self.compile_expr(value)?;
self.emit_name_store(location, value, &name)?;
}
Expr::Index(idx) => {
let val = self.compile_expr(value)?;
let base = self.compile_expr(&idx.expr)?;
let key = self.compile_expr(&idx.index)?;
self.emit(Op::SetIndex { base, key, val });
}
Expr::Field(f) => {
let val = self.compile_expr(value)?;
let base = self.compile_expr(&f.base)?;
let member = self.member_of(&f.member);
self.emit(Op::SetField { base, member, val });
}
Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => {
let val = self.compile_expr(value)?;
let target = self.compile_expr(&u.expr)?;
self.emit(Op::SetDeref { target, val });
}
Expr::Paren(p) => self.compile_assign(&p.expr, value)?,
_ => bail!("invalid assignment target"),
}
Ok(())
}
pub(super) fn compile_compound_assign(
&mut self,
target: &Expr,
op: BinKind,
rhs: &Expr,
) -> Result<()> {
match target {
Expr::Path(p) if p.path.segments.len() == 1 => {
let name = p.path.segments[0].ident.to_string();
let location = self.resolve_for_write(&name);
if let Some(imm) = int_literal(rhs) {
let current = self.load_name_location(location, &name)?;
let result = self.alloc();
self.emit(Op::BinImm {
dst: result,
a: current,
imm,
op,
});
self.emit_name_store(location, result, &name)?;
} else {
let b = self.compile_expr(rhs)?;
let current = self.load_name_location(location, &name)?;
let result = self.alloc();
self.emit(Op::Bin {
dst: result,
a: current,
b,
op,
});
self.emit_name_store(location, result, &name)?;
}
}
Expr::Index(idx) => {
let b = self.compile_expr(rhs)?;
let base = self.compile_expr(&idx.expr)?;
let key = self.compile_expr(&idx.index)?;
let cur = self.alloc();
self.emit(Op::Index {
dst: cur,
base,
key,
});
let res = self.alloc();
self.emit(Op::Bin {
dst: res,
a: cur,
b,
op,
});
self.emit(Op::SetIndex {
base,
key,
val: res,
});
}
Expr::Field(f) => {
let b = self.compile_expr(rhs)?;
let base = self.compile_expr(&f.base)?;
let member = self.member_of(&f.member);
let cur = self.alloc();
self.emit(Op::GetField {
dst: cur,
base,
member,
});
let res = self.alloc();
self.emit(Op::Bin {
dst: res,
a: cur,
b,
op,
});
self.emit(Op::SetField {
base,
member,
val: res,
});
}
Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => {
let b = self.compile_expr(rhs)?;
let target = self.compile_expr(&u.expr)?;
let current = self.alloc();
self.emit(Op::Deref {
dst: current,
src: target,
});
let result = self.alloc();
self.emit(Op::Bin {
dst: result,
a: current,
b,
op,
});
self.emit(Op::SetDeref {
target,
val: result,
});
}
_ => bail!("invalid compound assignment target"),
}
Ok(())
}
fn load_name_location(&mut self, location: NameLoc, name: &str) -> Result<Reg> {
match location {
NameLoc::Local(reg) => Ok(reg),
NameLoc::Cell(cell) => {
let reg = self.alloc();
self.emit(Op::LoadCell { dst: reg, cell });
Ok(reg)
}
NameLoc::Upvalue(idx) => {
let reg = self.alloc();
self.emit(Op::LoadUpvalue { dst: reg, idx });
Ok(reg)
}
NameLoc::None => bail!("assignment to unknown variable `{name}`"),
}
}
fn emit_name_store(&mut self, location: NameLoc, src: Reg, name: &str) -> Result<()> {
match location {
NameLoc::Local(dst) if dst != src => self.emit(Op::Move { dst, src }),
NameLoc::Local(_) => {}
NameLoc::Cell(cell) => self.emit(Op::StoreCell { cell, src }),
NameLoc::Upvalue(idx) => self.emit(Op::StoreUpvalue { idx, src }),
NameLoc::None => bail!("assignment to unknown variable `{name}`"),
}
Ok(())
}
pub(super) fn member_of(&mut self, member: &syn::Member) -> u16 {
match member {
syn::Member::Named(n) => self.add_member(Member::Named(n.to_string().into())),
syn::Member::Unnamed(i) => self.add_member(Member::Indexed(i.index as usize)),
}
}
pub(super) fn compile_struct_literal(&mut self, dst: Reg, s: &syn::ExprStruct) -> Result<()> {
let self_type = (s.path.segments.len() == 1 && s.path.segments[0].ident == "Self")
.then_some(self.ctx.impl_type)
.flatten();
let resolved = self_type.map(Arc::<str>::from).or_else(|| {
self.ctx
.resolver
.resolve_struct_key(self.ctx.module, &s.path)
});
let (name, def) = if let Some(canon) = resolved {
let def = self.ctx.resolver.structs.get(&canon).map(|d| d.ast.clone());
(canon.to_string(), def)
} else {
let bare = s
.path
.segments
.last()
.map(|seg| seg.ident.to_string())
.unwrap_or_default();
(bare, None)
};
let mut written: Vec<(String, &Expr)> = Vec::new();
for f in &s.fields {
let key = match &f.member {
syn::Member::Named(n) => n.to_string(),
syn::Member::Unnamed(i) => i.index.to_string(),
};
written.push((key, &f.expr));
}
let (order, renames): (Vec<String>, Vec<Option<Arc<str>>>) = match def {
Some(def) => {
let mut ordered: Vec<String> = def
.fields
.iter()
.filter_map(|f| f.ident.as_ref().map(std::string::ToString::to_string))
.filter(|k| written.iter().any(|(w, _)| w == k))
.collect();
for (k, _) in &written {
if !ordered.contains(k) {
ordered.push(k.clone());
}
}
let renames = ordered
.iter()
.map(|k| {
def.fields
.iter()
.find(|f| f.ident.as_ref().is_some_and(|i| i == k))
.and_then(serde_rename)
.map(Arc::<str>::from)
})
.collect();
(ordered, renames)
}
None => (written.iter().map(|(k, _)| k.clone()).collect(), Vec::new()),
};
let has_rest = s.rest.is_some();
let slots = order.len() + usize::from(has_rest);
let base = self.cur().reg_top;
for _ in 0..slots {
self.alloc();
}
for (i, fname) in order.iter().enumerate() {
let dstf = base + idx16(i);
match written.iter().find(|(k, _)| k == fname) {
Some((_, e)) => self.compile_into(dstf, e)?,
None => self.emit(Op::LoadUnit { dst: dstf }),
}
}
if let Some(rest) = &s.rest {
self.compile_into(base + idx16(order.len()), rest)?;
}
let info = {
let shape = StructShape::with_renames(
name,
order.into_iter().map(Into::into).collect(),
renames,
);
let f = self.cur();
f.struct_lits.push(StructLit { shape, has_rest });
idx16(f.struct_lits.len() - 1)
};
self.emit(Op::MakeStruct { dst, info, base });
Ok(())
}
pub(super) fn pattern_info(&mut self, pat: &Pat) -> Result<u16> {
let mut names = Vec::new();
collect_pattern_names(pat, &mut names);
let mut binds = Vec::new();
for n in names {
let reg = self.alloc();
self.define(&n, reg);
binds.push((n, reg));
}
let f = self.cur();
f.pats.push(PatInfo {
pat: lower_pattern(pat),
binds,
});
Ok(u16::try_from(f.pats.len() - 1)?)
}
pub(super) fn bind_pattern_irrefutable(&mut self, pat: &Pat, reg: Reg) -> Result<()> {
match pat {
Pat::Ident(id) if id.subpat.is_none() => {
self.define(&id.ident.to_string(), reg);
Ok(())
}
Pat::Wild(_) => Ok(()),
Pat::Type(t) => self.bind_pattern_irrefutable(&t.pat, reg),
Pat::Paren(p) => self.bind_pattern_irrefutable(&p.pat, reg),
Pat::Reference(r) => self.bind_pattern_irrefutable(&r.pat, reg),
_ => {
let matched = self.alloc();
let pidx = self.pattern_info(pat)?;
self.emit(Op::TestBind {
val: reg,
pat: pidx,
dst: matched,
});
Ok(())
}
}
}
}
fn is_transparent_new(segments: &[String]) -> bool {
let Some((prefix, [receiver, method])) = segments.split_last_chunk::<2>() else {
return false;
};
(prefix.is_empty() || matches!(prefix.first().map(String::as_str), Some("std" | "alloc")))
&& method == "new"
&& matches!(receiver.as_str(), "Box" | "Rc" | "Arc" | "RefCell" | "Cell")
}
pub(super) fn is_unit_variant_ident(id: &syn::PatIdent) -> bool {
id.by_ref.is_none()
&& id.mutability.is_none()
&& id.subpat.is_none()
&& id
.ident
.to_string()
.chars()
.next()
.is_some_and(|c| c.is_ascii_uppercase())
}
fn lower_pattern(pattern: &Pat) -> PPat {
match pattern {
Pat::Wild(_) => PPat::Wild,
Pat::Rest(_) => PPat::Rest,
Pat::Ident(ident) if is_unit_variant_ident(ident) => PPat::Path {
name: Some(ident.ident.to_string()),
},
Pat::Ident(ident) => PPat::Ident {
name: ident.ident.to_string(),
sub: ident
.subpat
.as_ref()
.map(|subpattern| Box::new(lower_pattern(&subpattern.1))),
},
Pat::Lit(literal) => lower_literal(&literal.lit),
Pat::Paren(paren) => lower_pattern(&paren.pat),
Pat::Reference(reference) => lower_pattern(&reference.pat),
Pat::Type(typed) => lower_pattern(&typed.pat),
Pat::Tuple(tuple) => PPat::Tuple(tuple.elems.iter().map(lower_pattern).collect()),
Pat::TupleStruct(tuple) => PPat::TupleStruct {
name: tuple
.path
.segments
.last()
.map(|segment| segment.ident.to_string()),
elems: tuple.elems.iter().map(lower_pattern).collect(),
},
Pat::Path(path) => PPat::Path {
name: path
.path
.segments
.last()
.map(|segment| segment.ident.to_string()),
},
Pat::Struct(structure) => PPat::Struct {
name: structure
.path
.segments
.last()
.map(|segment| segment.ident.to_string()),
fields: structure
.fields
.iter()
.map(|field| {
let name = match &field.member {
syn::Member::Named(name) => name.to_string(),
syn::Member::Unnamed(index) => index.index.to_string(),
};
(name, lower_pattern(&field.pat))
})
.collect(),
},
Pat::Or(or) => PPat::Or(or.cases.iter().map(lower_pattern).collect()),
Pat::Slice(slice) => PPat::Slice(slice.elems.iter().map(lower_pattern).collect()),
Pat::Range(range) => lower_range(range),
_ => PPat::Unsupported,
}
}
fn lower_range(range: &syn::PatRange) -> PPat {
let endpoint = |e: &Option<Box<Expr>>| match e {
Some(e) => endpoint_lit(e).map(Some),
None => Some(None),
};
let (Some(lo), Some(hi)) = (endpoint(&range.start), endpoint(&range.end)) else {
return PPat::Unsupported;
};
PPat::Range {
lo,
hi,
inclusive: matches!(range.limits, syn::RangeLimits::Closed(_)),
}
}
fn endpoint_lit(e: &Expr) -> Option<PLit> {
match e {
Expr::Lit(l) => match &l.lit {
Lit::Int(value) => value.base10_parse().ok().map(PLit::Int),
Lit::Float(value) => value.base10_parse().ok().map(PLit::Float),
Lit::Char(value) => Some(PLit::Char(value.value())),
Lit::Byte(value) => Some(PLit::Int(i64::from(value.value()))),
_ => None,
},
Expr::Unary(u) if matches!(u.op, syn::UnOp::Neg(_)) => match endpoint_lit(&u.expr) {
Some(PLit::Int(n)) => Some(PLit::Int(-n)),
Some(PLit::Float(f)) => Some(PLit::Float(-f)),
_ => None,
},
Expr::Paren(p) => endpoint_lit(&p.expr),
Expr::Group(g) => endpoint_lit(&g.expr),
Expr::Path(p) if p.path.segments.len() == 2 => {
let ty = p.path.segments[0].ident.to_string();
let which = p.path.segments[1].ident.to_string();
int_type_bound(&ty, &which).map(PLit::Int)
}
_ => None,
}
}
fn int_type_bound(ty: &str, which: &str) -> Option<i64> {
let (lo, hi) = match ty {
"i8" => (i64::from(i8::MIN), i64::from(i8::MAX)),
"i16" => (i64::from(i16::MIN), i64::from(i16::MAX)),
"i32" => (i64::from(i32::MIN), i64::from(i32::MAX)),
"i64" | "isize" | "i128" => (i64::MIN, i64::MAX),
"u8" => (0, i64::from(u8::MAX)),
"u16" => (0, i64::from(u16::MAX)),
"u32" => (0, i64::from(u32::MAX)),
"u64" | "usize" | "u128" => (0, i64::MAX),
_ => return None,
};
match which {
"MIN" => Some(lo),
"MAX" => Some(hi),
_ => None,
}
}
fn lower_literal(literal: &Lit) -> PPat {
match literal {
Lit::Int(value) => value
.base10_parse()
.map_or(PPat::Unsupported, |value| PPat::Lit(PLit::Int(value))),
Lit::Float(value) => value
.base10_parse()
.map_or(PPat::Unsupported, |value| PPat::Lit(PLit::Float(value))),
Lit::Bool(value) => PPat::Lit(PLit::Bool(value.value)),
Lit::Str(value) => PPat::Lit(PLit::Str(value.value())),
Lit::Char(value) => PPat::Lit(PLit::Char(value.value())),
Lit::Byte(value) => PPat::Lit(PLit::Int(i64::from(value.value()))),
_ => PPat::Unsupported,
}
}
fn is_tokio_spawn(path: &syn::Path) -> bool {
let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
segs.last().map(String::as_str) == Some("spawn") && segs.iter().any(|s| s == "tokio")
}
fn turbofish_collect_target(tf: &syn::AngleBracketedGenericArguments) -> Option<CollectTarget> {
tf.args.iter().find_map(|arg| match arg {
syn::GenericArgument::Type(ty) => CollectTarget::of_type(ty),
_ => None,
})
}
struct TyEnv<'a> {
locals: &'a HashMap<String, ScalarTy>,
fn_returns: &'a HashMap<String, ScalarTy>,
}
fn option_payload(expr: &Expr, env: &TyEnv) -> Option<ScalarTy> {
match expr {
Expr::Paren(inner) => option_payload(&inner.expr, env),
Expr::Group(inner) => option_payload(&inner.expr, env),
Expr::Block(block) => block_tail(&block.block).and_then(|e| option_payload(e, env)),
Expr::If(sel) => block_tail(&sel.then_branch)
.and_then(|e| option_payload(e, env))
.or_else(|| {
sel.else_branch
.as_ref()
.and_then(|(_, e)| option_payload(e, env))
}),
Expr::Path(path) => {
let segment = path.path.segments.last()?;
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
return turbofish_scalar(Some(args));
}
match env.locals.get(&segment.ident.to_string()) {
Some(ScalarTy::Opt(payload)) => Some((**payload).clone()),
_ => None,
}
}
Expr::Call(call) => {
let Expr::Path(path) = &*call.func else {
return None;
};
let last = path.path.segments.last()?;
(last.ident == "Some")
.then(|| call.args.first().and_then(|a| written_ty(a, env)))
.flatten()
}
Expr::MethodCall(call) => match call.method.to_string().as_str() {
"then_some" => call.args.first().and_then(|a| written_ty(a, env)),
"parse" => turbofish_scalar(call.turbofish.as_ref()),
"or" => call
.args
.first()
.and_then(|a| option_payload(a, env))
.or_else(|| option_payload(&call.receiver, env)),
"clone" | "cloned" | "copied" | "take" | "as_ref" | "as_mut" | "filter" | "ok" => {
option_payload(&call.receiver, env)
}
"unwrap_or_default" | "unwrap" | "expect" => {
option_payload(&call.receiver, env)?.payload().cloned()
}
"unwrap_or" => option_payload(&call.receiver, env)
.and_then(|payload| payload.payload().cloned())
.or_else(|| call.args.first().and_then(|a| option_payload(a, env))),
"get" => element_ty(&call.receiver, env).or_else(|| map_value_ty(&call.receiver, env)),
"first" | "last" | "pop" => element_ty(&call.receiver, env),
"min" | "max" if call.args.is_empty() => element_ty(&call.receiver, env),
"checked_add" | "checked_sub" | "checked_mul" | "checked_div" | "checked_rem"
| "checked_neg" | "checked_abs" | "checked_pow" | "checked_shl" | "checked_shr"
| "checked_div_euclid" | "checked_rem_euclid" => {
match written_ty(&call.receiver, env) {
Some(ty @ ScalarTy::Int(_)) => Some(ty),
_ => None,
}
}
_ => None,
},
_ => None,
}
}
fn block_tail(block: &syn::Block) -> Option<&Expr> {
match block.stmts.last()? {
syn::Stmt::Expr(expr, None) => Some(expr),
_ => None,
}
}
fn element_ty(expr: &Expr, env: &TyEnv) -> Option<ScalarTy> {
match expr {
Expr::Paren(inner) => element_ty(&inner.expr, env),
Expr::Group(inner) => element_ty(&inner.expr, env),
Expr::Block(block) => block_tail(&block.block).and_then(|e| element_ty(e, env)),
Expr::If(sel) => block_tail(&sel.then_branch)
.and_then(|e| element_ty(e, env))
.or_else(|| {
sel.else_branch
.as_ref()
.and_then(|(_, e)| element_ty(e, env))
}),
Expr::Path(path) => {
let segment = path.path.segments.last()?;
match env.locals.get(&segment.ident.to_string()) {
Some(ScalarTy::List(element)) => Some((**element).clone()),
_ => None,
}
}
Expr::Macro(mac) if mac.mac.path.is_ident("vec") => vec_macro_element(&mac.mac, env),
Expr::Call(_) => match written_ty(expr, env) {
Some(ScalarTy::List(element)) => Some(*element),
_ => None,
},
Expr::MethodCall(call) => match call.method.to_string().as_str() {
"iter" | "into_iter" | "cloned" | "copied" | "clone" | "to_vec" | "rev" => {
element_ty(&call.receiver, env)
}
"map" => match call.args.first() {
Some(Expr::Closure(closure)) => written_ty(&closure.body, env),
_ => None,
},
"collect" => match turbofish_scalar(call.turbofish.as_ref()) {
Some(ScalarTy::List(element)) => Some(*element),
_ => None,
},
"unwrap" | "unwrap_or" | "unwrap_or_default" => {
let from_receiver = match option_payload(&call.receiver, env) {
Some(ScalarTy::List(element)) => Some(*element),
_ => None,
};
from_receiver.or_else(|| call.args.first().and_then(|a| element_ty(a, env)))
}
_ => None,
},
_ => None,
}
}
fn vec_macro_element(mac: &syn::Macro, env: &TyEnv) -> Option<ScalarTy> {
use syn::Token;
use syn::punctuated::Punctuated;
if let Ok(elements) = mac.parse_body_with(Punctuated::<Expr, Token![,]>::parse_terminated) {
return elements.iter().find_map(|e| written_ty(e, env));
}
mac.parse_body_with(Punctuated::<Expr, Token![;]>::parse_terminated)
.ok()?
.first()
.and_then(|e| written_ty(e, env))
}
fn written_ty(expr: &Expr, env: &TyEnv) -> Option<ScalarTy> {
match expr {
Expr::Paren(inner) => written_ty(&inner.expr, env),
Expr::Group(inner) => written_ty(&inner.expr, env),
Expr::Block(block) => block_tail(&block.block).and_then(|e| written_ty(e, env)),
Expr::Cast(cast) => ScalarTy::lower(&cast.ty),
Expr::Binary(bin) => {
use syn::BinOp::{
Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl,
Shr, Sub,
};
match bin.op {
Add(_) | Sub(_) | Mul(_) | Div(_) | Rem(_) | BitAnd(_) | BitOr(_) | BitXor(_) => {
written_ty(&bin.left, env).or_else(|| written_ty(&bin.right, env))
}
Shl(_) | Shr(_) => written_ty(&bin.left, env),
Eq(_) | Ne(_) | Lt(_) | Le(_) | Gt(_) | Ge(_) | And(_) | Or(_) => {
Some(ScalarTy::Bool)
}
_ => None,
}
}
Expr::Unary(un) => match un.op {
syn::UnOp::Neg(_) | syn::UnOp::Not(_) => written_ty(&un.expr, env),
_ => None,
},
Expr::Lit(lit) => match &lit.lit {
Lit::Str(_) => Some(ScalarTy::Str),
Lit::Bool(_) => Some(ScalarTy::Bool),
Lit::Char(_) => Some(ScalarTy::Char),
Lit::Int(int) => {
crate::interpreter::numeric::IntWidth::parse(int.suffix()).map(ScalarTy::Int)
}
Lit::Float(float) => match float.suffix() {
"f32" => Some(ScalarTy::F32),
"f64" => Some(ScalarTy::F64),
_ => None,
},
_ => None,
},
Expr::MethodCall(call) if call.method == "clone" => written_ty(&call.receiver, env),
Expr::MethodCall(call)
if matches!(
call.method.to_string().as_str(),
"unwrap" | "expect" | "unwrap_or" | "unwrap_or_default"
) && option_payload(&call.receiver, env).is_some() =>
{
option_payload(&call.receiver, env)
}
Expr::Call(_) | Expr::Path(_) | Expr::MethodCall(_) => {
if let Some(payload) = option_payload(expr, env) {
Some(ScalarTy::Opt(Box::new(payload)))
} else if is_none_path(expr) {
Some(ScalarTy::Opt(Box::new(ScalarTy::Other)))
} else if let Some(element) = vec_new_element(expr) {
Some(ScalarTy::List(Box::new(element)))
} else if let Some(container) = container_new_ty(expr) {
Some(container)
} else if is_string_call(expr) {
Some(ScalarTy::Str)
} else if let Expr::Path(path) = expr
&& path.path.segments.len() == 1
&& let Some(declared) = env.locals.get(&path.path.segments[0].ident.to_string())
{
Some(declared.clone())
} else {
fn_return_ty(expr, env)
}
}
Expr::Macro(mac) if mac.mac.path.is_ident("vec") => Some(ScalarTy::List(Box::new(
vec_macro_element(&mac.mac, env).unwrap_or(ScalarTy::Other),
))),
_ => None,
}
}
fn fn_return_ty(expr: &Expr, env: &TyEnv) -> Option<ScalarTy> {
let Expr::Call(call) = expr else {
return None;
};
let Expr::Path(path) = &*call.func else {
return None;
};
let segment = path.path.segments.last()?;
env.fn_returns.get(&segment.ident.to_string()).cloned()
}
fn is_string_call(expr: &Expr) -> bool {
let Expr::Call(call) = expr else {
return false;
};
let Expr::Path(path) = &*call.func else {
return false;
};
let mut segments = path.path.segments.iter().rev();
let is_ctor = segments
.next()
.is_some_and(|s| s.ident == "from" || s.ident == "new");
is_ctor && segments.next().is_some_and(|s| s.ident == "String")
}
fn vec_new_element(expr: &Expr) -> Option<ScalarTy> {
let Expr::Call(call) = expr else {
return None;
};
let Expr::Path(path) = &*call.func else {
return None;
};
let mut segments = path.path.segments.iter().rev();
let last = segments.next()?;
if last.ident != "new" {
return None;
}
let container = segments.next()?;
if container.ident != "Vec" && container.ident != "VecDeque" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &container.arguments else {
return None;
};
turbofish_scalar(Some(args))
}
fn container_new_ty(expr: &Expr) -> Option<ScalarTy> {
let Expr::Call(call) = expr else {
return None;
};
let Expr::Path(path) = &*call.func else {
return None;
};
let mut segments = path.path.segments.iter().rev();
let last = segments.next()?;
if last.ident != "new" {
return None;
}
let container = segments.next()?;
let name = container.ident.to_string();
if !matches!(
name.as_str(),
"HashMap" | "BTreeMap" | "HashSet" | "BTreeSet"
) {
return None;
}
ScalarTy::lower_segment(container)
}
fn map_value_ty(expr: &Expr, env: &TyEnv) -> Option<ScalarTy> {
match expr {
Expr::Paren(inner) => map_value_ty(&inner.expr, env),
Expr::Group(inner) => map_value_ty(&inner.expr, env),
Expr::Block(block) => block_tail(&block.block).and_then(|e| map_value_ty(e, env)),
Expr::Path(path) => {
let segment = path.path.segments.last()?;
match env.locals.get(&segment.ident.to_string()) {
Some(ScalarTy::Map(value)) => Some((**value).clone()),
_ => None,
}
}
Expr::Call(_) => match container_new_ty(expr) {
Some(ScalarTy::Map(value)) => Some(*value),
_ => None,
},
Expr::MethodCall(call) if call.method == "clone" => map_value_ty(&call.receiver, env),
_ => None,
}
}
fn is_none_path(expr: &Expr) -> bool {
matches!(expr, Expr::Path(path)
if path.path.segments.last().is_some_and(|s| s.ident == "None"))
}
fn turbofish_scalar(args: Option<&syn::AngleBracketedGenericArguments>) -> Option<ScalarTy> {
args?
.args
.iter()
.find_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.and_then(ScalarTy::lower)
}