use core::mem::{replace, take};
use core::ops::Neg;
use num::ToPrimitive;
use tracing::instrument_ast;
use crate::alloc::prelude::*;
use crate::alloc::try_format;
use crate::alloc::{self, Box, HashMap, HashSet};
use crate::ast::{self, NumberSize, Spanned};
use crate::compile::meta;
use crate::compile::{self, ErrorKind, WithSpan};
use crate::hash::ParametersBuilder;
use crate::hir;
use crate::parse::Resolve;
use crate::query::AsyncBlock;
use crate::query::Closure;
use crate::query::SecondaryBuildEntry;
use crate::query::{self, GenericsParameters, Named, SecondaryBuild};
use crate::runtime::{ConstValue, ConstValueKind, Inline, Type, TypeCheck};
use crate::{Hash, Item};
use super::{Ctxt, Needs};
#[instrument_ast(span = span)]
pub(crate) fn empty_fn<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::EmptyBlock,
span: &dyn Spanned,
) -> compile::Result<hir::ItemFn<'hir>> {
Ok(hir::ItemFn {
span: span.span(),
args: &[],
body: statements(cx, None, &ast.statements, span)?,
})
}
#[instrument_ast(span = ast)]
pub(crate) fn item_fn<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ItemFn,
) -> compile::Result<hir::ItemFn<'hir>> {
alloc_with!(cx, ast);
Ok(hir::ItemFn {
span: ast.span(),
args: iter!(&ast.args, |(ast, _)| fn_arg(cx, ast)?),
body: block(cx, None, &ast.body)?,
})
}
#[instrument_ast(span = ast)]
fn expr_call_closure<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprClosure,
) -> compile::Result<hir::ExprKind<'hir>> {
alloc_with!(cx, ast);
let item =
cx.q.item_for("lowering closure call", ast.id)
.with_span(ast)?;
let Some(meta) = cx.q.query_meta(ast, item.item, Default::default())? else {
return Err(compile::Error::new(
ast,
ErrorKind::MissingItem {
item: cx.q.pool.item(item.item).try_to_owned()?,
},
));
};
let meta::Kind::Closure { call, do_move, .. } = meta.kind else {
return Err(compile::Error::expected_meta(
ast,
meta.info(cx.q.pool)?,
"a closure",
));
};
tracing::trace!("queuing closure build entry");
cx.scopes.push_captures()?;
let args = iter!(ast.args.as_slice(), |(arg, _)| fn_arg(cx, arg)?);
let body = alloc!(expr(cx, &ast.body)?);
let layer = cx.scopes.pop().with_span(&ast.body)?;
cx.q.set_used(&meta.item_meta)?;
let captures = &*iter!(layer.captures().map(|(_, id)| id));
let Some(queue) = cx.secondary_builds.as_mut() else {
return Err(compile::Error::new(ast, ErrorKind::ClosureInConst));
};
queue.try_push(SecondaryBuildEntry {
item_meta: meta.item_meta,
build: SecondaryBuild::Closure(Closure {
hir: alloc!(hir::ExprClosure {
args,
body,
captures,
}),
call,
}),
})?;
if captures.is_empty() {
return Ok(hir::ExprKind::Fn(meta.hash));
}
Ok(hir::ExprKind::CallClosure(alloc!(hir::ExprCallClosure {
hash: meta.hash,
do_move,
captures,
})))
}
#[inline]
pub(crate) fn block<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
label: Option<&(ast::Label, T![:])>,
ast: &ast::Block,
) -> compile::Result<hir::Block<'hir>> {
statements(cx, label, &ast.statements, ast)
}
#[instrument_ast(span = span)]
fn statements<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
label: Option<&(ast::Label, T![:])>,
statements: &[ast::Stmt],
span: &dyn Spanned,
) -> compile::Result<hir::Block<'hir>> {
alloc_with!(cx, span);
let label = match label {
Some((label, _)) => Some(alloc_str!(label.resolve(resolve_context!(cx.q))?)),
None => None,
};
cx.scopes.push(label)?;
let at = cx.statements.len();
let mut value = None;
for ast in statements {
let last = match ast {
ast::Stmt::Local(ast) => {
let depacked = if ast.attributes.is_empty() && cx.q.options.lowering > 0 {
unpack_locals(cx, &ast.pat, &ast.expr)?
} else {
false
};
if !depacked {
let stmt = hir::Stmt::Local(alloc!(local(cx, ast)?));
cx.statement_buffer.try_push(stmt)?;
}
value.take()
}
ast::Stmt::Expr(ast) => {
if let Some(stmt) = value.replace(&*alloc!(expr(cx, ast)?)).map(hir::Stmt::Expr) {
cx.statement_buffer.try_push(stmt)?;
}
None
}
ast::Stmt::Semi(ast) => {
let stmt = hir::Stmt::Expr(alloc!(expr(cx, &ast.expr)?));
cx.statement_buffer.try_push(stmt)?;
value.take()
}
ast::Stmt::Item(..) => continue,
};
if let Some(last) = last {
cx.statements
.try_push(hir::Stmt::Expr(last))
.with_span(span)?;
}
for stmt in cx.statement_buffer.drain(..) {
cx.statements.try_push(stmt).with_span(span)?;
}
}
let statements = iter!(cx.statements.drain(at..));
let layer = cx.scopes.pop().with_span(span)?;
Ok(hir::Block {
span: span.span(),
label,
statements,
value,
drop: iter!(layer.into_drop_order()),
})
}
#[instrument_ast(span = ast)]
fn expr_range<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprRange,
) -> compile::Result<hir::ExprRange<'hir>> {
match (ast.start.as_deref(), ast.end.as_deref(), &ast.limits) {
(Some(start), None, ast::ExprRangeLimits::HalfOpen(..)) => Ok(hir::ExprRange::RangeFrom {
start: expr(cx, start)?,
}),
(None, None, ast::ExprRangeLimits::HalfOpen(..)) => Ok(hir::ExprRange::RangeFull),
(Some(start), Some(end), ast::ExprRangeLimits::Closed(..)) => {
Ok(hir::ExprRange::RangeInclusive {
start: expr(cx, start)?,
end: expr(cx, end)?,
})
}
(None, Some(end), ast::ExprRangeLimits::Closed(..)) => {
Ok(hir::ExprRange::RangeToInclusive {
end: expr(cx, end)?,
})
}
(None, Some(end), ast::ExprRangeLimits::HalfOpen(..)) => Ok(hir::ExprRange::RangeTo {
end: expr(cx, end)?,
}),
(Some(start), Some(end), ast::ExprRangeLimits::HalfOpen(..)) => Ok(hir::ExprRange::Range {
start: expr(cx, start)?,
end: expr(cx, end)?,
}),
(Some(..) | None, None, ast::ExprRangeLimits::Closed(..)) => Err(compile::Error::msg(
ast,
"Unsupported range, you probably want `..` instead of `..=`",
)),
}
}
#[instrument_ast(span = ast)]
fn expr_object<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprObject,
) -> compile::Result<hir::ExprKind<'hir>> {
alloc_with!(cx, ast);
let span = ast;
let mut keys_dup = HashMap::new();
let assignments = &mut *iter!(&ast.assignments, |(ast, _)| {
let key = object_key(cx, &ast.key)?;
if let Some(_existing) = keys_dup.try_insert(key.1, key.0)? {
return Err(compile::Error::new(
key.0,
ErrorKind::DuplicateObjectKey {
#[cfg(feature = "emit")]
existing: _existing.span(),
#[cfg(feature = "emit")]
object: key.0.span(),
},
));
}
let assign = match &ast.assign {
Some((_, ast)) => expr(cx, ast)?,
None => {
let Some((name, _)) = cx.scopes.get(hir::Name::Str(key.1))? else {
return Err(compile::Error::new(
key.0,
ErrorKind::MissingLocal {
name: key.1.try_to_string()?.try_into()?,
},
));
};
hir::Expr {
span: ast.span(),
kind: hir::ExprKind::Variable(name),
}
}
};
hir::FieldAssign {
key: (key.0.span(), key.1),
assign,
position: None,
}
});
let mut check_object_fields = |fields: &[meta::FieldMeta], item: &Item| {
let mut named = HashMap::new();
for f in fields.iter() {
named.try_insert(f.name.as_ref(), f)?;
}
for assign in assignments.iter_mut() {
match named.remove(assign.key.1) {
Some(field_meta) => {
assign.position = Some(field_meta.position);
}
None => {
return Err(compile::Error::new(
assign.key.0,
ErrorKind::LitObjectNotField {
field: assign.key.1.try_into()?,
item: item.try_to_owned()?,
},
));
}
};
}
if let Some(field) = named.into_keys().next() {
return Err(compile::Error::new(
span,
ErrorKind::LitObjectMissingField {
field: field.try_into()?,
item: item.try_to_owned()?,
},
));
}
Ok(())
};
let kind = match &ast.ident {
ast::ObjectIdent::Named(path) => {
let named = cx.q.convert_path(path)?;
let parameters = generics_parameters(cx, &named)?;
let meta = cx.lookup_meta(path, named.item, parameters)?;
let item = cx.q.pool.item(meta.item_meta.item);
match &meta.kind {
meta::Kind::Struct {
fields: meta::Fields::Empty,
..
} => {
check_object_fields(&[], item)?;
hir::ExprObjectKind::EmptyStruct { hash: meta.hash }
}
meta::Kind::Struct {
fields: meta::Fields::Named(st),
constructor,
..
} => {
check_object_fields(&st.fields, item)?;
match constructor {
Some(_) => hir::ExprObjectKind::ExternalType {
hash: meta.hash,
args: st.fields.len(),
},
None => hir::ExprObjectKind::Struct { hash: meta.hash },
}
}
meta::Kind::Variant {
fields: meta::Fields::Named(st),
..
} => {
check_object_fields(&st.fields, item)?;
hir::ExprObjectKind::StructVariant { hash: meta.hash }
}
_ => {
return Err(compile::Error::new(
span,
ErrorKind::UnsupportedLitObject {
meta: meta.info(cx.q.pool)?,
},
));
}
}
}
ast::ObjectIdent::Anonymous(..) => hir::ExprObjectKind::Anonymous,
};
Ok(hir::ExprKind::Object(alloc!(hir::ExprObject {
kind,
assignments,
})))
}
#[instrument_ast(span = ast)]
pub(crate) fn expr<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::Expr,
) -> compile::Result<hir::Expr<'hir>> {
alloc_with!(cx, ast);
let in_path = take(&mut cx.in_path);
let kind = match ast {
ast::Expr::Path(ast) => expr_path(cx, ast, in_path)?,
ast::Expr::Assign(ast) => hir::ExprKind::Assign(alloc!(hir::ExprAssign {
lhs: expr(cx, &ast.lhs)?,
rhs: expr(cx, &ast.rhs)?,
})),
ast::Expr::While(ast) => {
let label = match &ast.label {
Some((label, _)) => Some(alloc_str!(label.resolve(resolve_context!(cx.q))?)),
None => None,
};
cx.scopes.push_loop(label)?;
let condition = condition(cx, &ast.condition)?;
let body = block(cx, None, &ast.body)?;
let layer = cx.scopes.pop().with_span(ast)?;
hir::ExprKind::Loop(alloc!(hir::ExprLoop {
label,
condition: Some(alloc!(condition)),
body,
drop: iter!(layer.into_drop_order()),
}))
}
ast::Expr::Loop(ast) => {
let label = match &ast.label {
Some((label, _)) => Some(alloc_str!(label.resolve(resolve_context!(cx.q))?)),
None => None,
};
cx.scopes.push_loop(label)?;
let body = block(cx, None, &ast.body)?;
let layer = cx.scopes.pop().with_span(ast)?;
let kind = hir::ExprKind::Loop(alloc!(hir::ExprLoop {
label,
condition: None,
body,
drop: iter!(layer.into_drop_order()),
}));
kind
}
ast::Expr::For(ast) => {
let iter = expr(cx, &ast.iter)?;
let label = match &ast.label {
Some((label, _)) => Some(alloc_str!(label.resolve(resolve_context!(cx.q))?)),
None => None,
};
cx.scopes.push_loop(label)?;
let binding = pat_binding(cx, &ast.binding)?;
let body = block(cx, None, &ast.body)?;
let layer = cx.scopes.pop().with_span(ast)?;
hir::ExprKind::For(alloc!(hir::ExprFor {
label,
binding,
iter,
body,
drop: iter!(layer.into_drop_order()),
}))
}
ast::Expr::Let(ast) => hir::ExprKind::Let(alloc!(hir::ExprLet {
pat: pat_binding(cx, &ast.pat)?,
expr: expr(cx, &ast.expr)?,
})),
ast::Expr::If(ast) => hir::ExprKind::If(alloc!(expr_if(cx, ast)?)),
ast::Expr::Match(ast) => hir::ExprKind::Match(alloc!(hir::ExprMatch {
expr: alloc!(expr(cx, &ast.expr)?),
branches: iter!(&ast.branches, |(ast, _)| {
cx.scopes.push(None)?;
let pat = pat_binding(cx, &ast.pat)?;
let condition = option!(&ast.condition, |(_, ast)| expr(cx, ast)?);
let body = expr(cx, &ast.body)?;
let layer = cx.scopes.pop().with_span(ast)?;
hir::ExprMatchBranch {
span: ast.span(),
pat,
condition,
body,
drop: iter!(layer.into_drop_order()),
}
}),
})),
ast::Expr::Call(ast) => hir::ExprKind::Call(alloc!(expr_call(cx, ast)?)),
ast::Expr::FieldAccess(ast) => {
hir::ExprKind::FieldAccess(alloc!(expr_field_access(cx, ast)?))
}
ast::Expr::Empty(ast) => {
cx.in_path = in_path;
hir::ExprKind::Group(alloc!(expr(cx, &ast.expr)?))
}
ast::Expr::Binary(ast) => {
let rhs_needs = match &ast.op {
ast::BinOp::As(..) | ast::BinOp::Is(..) | ast::BinOp::IsNot(..) => Needs::Type,
_ => Needs::Value,
};
let lhs = expr(cx, &ast.lhs)?;
let needs = replace(&mut cx.needs, rhs_needs);
let rhs = expr(cx, &ast.rhs)?;
cx.needs = needs;
hir::ExprKind::Binary(alloc!(hir::ExprBinary {
lhs,
op: ast.op,
rhs,
}))
}
ast::Expr::Unary(ast) => expr_unary(cx, ast)?,
ast::Expr::Index(ast) => hir::ExprKind::Index(alloc!(hir::ExprIndex {
target: expr(cx, &ast.target)?,
index: expr(cx, &ast.index)?,
})),
ast::Expr::Block(ast) => expr_block(cx, ast)?,
ast::Expr::Break(ast) => hir::ExprKind::Break(alloc!(expr_break(cx, ast)?)),
ast::Expr::Continue(ast) => hir::ExprKind::Continue(alloc!(expr_continue(cx, ast)?)),
ast::Expr::Yield(ast) => hir::ExprKind::Yield(option!(&ast.expr, |ast| expr(cx, ast)?)),
ast::Expr::Return(ast) => hir::ExprKind::Return(option!(&ast.expr, |ast| expr(cx, ast)?)),
ast::Expr::Await(ast) => hir::ExprKind::Await(alloc!(expr(cx, &ast.expr)?)),
ast::Expr::Try(ast) => hir::ExprKind::Try(alloc!(expr(cx, &ast.expr)?)),
ast::Expr::Select(ast) => {
let mut default = None;
let mut branches = Vec::new();
let mut exprs = Vec::new();
for (ast, _) in &ast.branches {
match ast {
ast::ExprSelectBranch::Pat(ast) => {
cx.scopes.push(None)?;
let pat = pat_binding(cx, &ast.pat)?;
let body = expr(cx, &ast.body)?;
let layer = cx.scopes.pop().with_span(&ast)?;
exprs.try_push(expr(cx, &ast.expr)?).with_span(&ast.expr)?;
branches.try_push(hir::ExprSelectBranch {
pat,
body,
drop: iter!(layer.into_drop_order()),
})?;
}
ast::ExprSelectBranch::Default(ast) => {
if default.is_some() {
return Err(compile::Error::new(
ast,
ErrorKind::SelectMultipleDefaults,
));
}
default = Some(alloc!(expr(cx, &ast.body)?));
}
}
}
hir::ExprKind::Select(alloc!(hir::ExprSelect {
branches: iter!(branches),
exprs: iter!(exprs),
default: option!(default),
}))
}
ast::Expr::Closure(ast) => expr_call_closure(cx, ast)?,
ast::Expr::Lit(ast) => hir::ExprKind::Lit(lit(cx, &ast.lit)?),
ast::Expr::Object(ast) => expr_object(cx, ast)?,
ast::Expr::Tuple(ast) => hir::ExprKind::Tuple(alloc!(hir::ExprSeq {
items: iter!(&ast.items, |(ast, _)| expr(cx, ast)?),
})),
ast::Expr::Vec(ast) => hir::ExprKind::Vec(alloc!(hir::ExprSeq {
items: iter!(&ast.items, |(ast, _)| expr(cx, ast)?),
})),
ast::Expr::Range(ast) => hir::ExprKind::Range(alloc!(expr_range(cx, ast)?)),
ast::Expr::Group(ast) => hir::ExprKind::Group(alloc!(expr(cx, &ast.expr)?)),
ast::Expr::MacroCall(ast) => {
let Some(id) = ast.id else {
return Err(compile::Error::msg(ast, "missing expanded macro id"));
};
match cx.q.builtin_macro_for(id).with_span(ast)?.as_ref() {
query::BuiltInMacro::Template(ast) => {
let old = replace(&mut cx.in_template, true);
let result = hir::ExprKind::Template(alloc!(hir::BuiltInTemplate {
span: ast.span,
from_literal: ast.from_literal,
exprs: iter!(&ast.exprs, |ast| expr(cx, ast)?),
}));
cx.in_template = old;
result
}
query::BuiltInMacro::Format(ast) => {
let spec = hir::BuiltInFormatSpec {
fill: ast.fill,
align: ast.align,
width: ast.width,
precision: ast.precision,
flags: ast.flags,
format_type: ast.format_type,
};
hir::ExprKind::Format(alloc!(hir::BuiltInFormat {
spec,
value: alloc!(expr(cx, &ast.value)?),
}))
}
query::BuiltInMacro::File(ast) => hir::ExprKind::Lit(lit(cx, &ast.value)?),
query::BuiltInMacro::Line(ast) => hir::ExprKind::Lit(lit(cx, &ast.value)?),
}
}
};
Ok(hir::Expr {
span: ast.span(),
kind,
})
}
#[instrument_ast(span = span)]
fn pat_const_value<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
const_value: &ConstValue,
span: &dyn Spanned,
) -> compile::Result<hir::Pat<'hir>> {
alloc_with!(cx, span);
let kind = 'kind: {
let lit = match *const_value.as_kind() {
ConstValueKind::Inline(value) => match value {
Inline::Unit => {
break 'kind hir::PatKind::Sequence(alloc!(hir::PatSequence {
kind: hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Unit,
count: 0,
is_open: false,
},
items: &[],
}));
}
Inline::Bool(b) => hir::Lit::Bool(b),
Inline::Char(ch) => hir::Lit::Char(ch),
Inline::Unsigned(integer) => hir::Lit::Unsigned(integer),
Inline::Signed(integer) => hir::Lit::Signed(integer),
_ => {
return Err(compile::Error::msg(
span,
"Unsupported constant value in pattern",
))
}
},
ConstValueKind::String(ref string) => hir::Lit::Str(alloc_str!(string.as_ref())),
ConstValueKind::Bytes(ref bytes) => hir::Lit::ByteStr(alloc_bytes!(bytes.as_ref())),
ConstValueKind::Vec(ref items) => {
let items = iter!(items.iter(), items.len(), |value| pat_const_value(
cx, value, span
)?);
break 'kind hir::PatKind::Sequence(alloc!(hir::PatSequence {
kind: hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Vec,
count: items.len(),
is_open: false,
},
items,
}));
}
ConstValueKind::Tuple(ref items) => {
let items = iter!(items.iter(), items.len(), |value| pat_const_value(
cx, value, span
)?);
break 'kind hir::PatKind::Sequence(alloc!(hir::PatSequence {
kind: hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Vec,
count: items.len(),
is_open: false,
},
items,
}));
}
ConstValueKind::Object(ref fields) => {
let bindings = iter!(fields.iter(), fields.len(), |(key, value)| {
let pat = alloc!(pat_const_value(cx, value, span)?);
hir::Binding::Binding(span.span(), alloc_str!(key.as_ref()), pat)
});
break 'kind hir::PatKind::Object(alloc!(hir::PatObject {
kind: hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Object,
count: bindings.len(),
is_open: false,
},
bindings,
}));
}
_ => {
return Err(compile::Error::msg(
span,
"Unsupported constant value in pattern",
));
}
};
hir::PatKind::Lit(alloc!(hir::Expr {
span: span.span(),
kind: hir::ExprKind::Lit(lit),
}))
};
Ok(hir::Pat {
span: span.span(),
kind,
})
}
#[instrument_ast(span = ast)]
fn expr_if<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprIf,
) -> compile::Result<hir::Conditional<'hir>> {
alloc_with!(cx, ast);
let length = 1 + ast.expr_else_ifs.len();
let then = [(
ast.if_.span().join(ast.block.span()),
&ast.condition,
&ast.block,
)]
.into_iter();
let else_ifs = ast
.expr_else_ifs
.iter()
.map(|ast| (ast.span(), &ast.condition, &ast.block));
let branches = iter!(then.chain(else_ifs), length, |(span, c, b)| {
cx.scopes.push(None)?;
let condition = condition(cx, c)?;
let block = block(cx, None, b)?;
let layer = cx.scopes.pop().with_span(ast)?;
let condition = &*alloc!(condition);
let drop = &*iter!(layer.into_drop_order());
hir::ConditionalBranch {
span,
condition,
block,
drop,
}
});
let fallback = match &ast.expr_else {
Some(ast) => Some(&*alloc!(block(cx, None, &ast.block)?)),
None => None,
};
Ok(hir::Conditional { branches, fallback })
}
#[instrument_ast(span = ast)]
fn lit<'hir>(cx: &mut Ctxt<'hir, '_, '_>, ast: &ast::Lit) -> compile::Result<hir::Lit<'hir>> {
alloc_with!(cx, ast);
match ast {
ast::Lit::Bool(lit) => Ok(hir::Lit::Bool(lit.value)),
ast::Lit::Number(lit) => {
let n = lit.resolve(resolve_context!(cx.q))?;
match (n.value, n.suffix) {
(ast::NumberValue::Float(n), _) => Ok(hir::Lit::Float(n)),
(ast::NumberValue::Integer(int), Some(ast::NumberSuffix::Unsigned(_, size))) => {
let Some(n) = int.to_u64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadUnsignedOutOfBounds { size },
));
};
if !size.unsigned_in(n) {
return Err(compile::Error::new(
ast,
ErrorKind::BadUnsignedOutOfBounds { size },
));
}
Ok(hir::Lit::Unsigned(n))
}
(ast::NumberValue::Integer(int), Some(ast::NumberSuffix::Signed(_, size))) => {
let Some(n) = int.to_i64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds { size },
));
};
if !size.signed_in(n) {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds { size },
));
}
Ok(hir::Lit::Signed(n))
}
(ast::NumberValue::Integer(int), _) => {
let Some(n) = int.to_i64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds {
size: NumberSize::S64,
},
));
};
Ok(hir::Lit::Signed(n))
}
}
}
ast::Lit::Byte(lit) => {
let b = lit.resolve(resolve_context!(cx.q))?;
Ok(hir::Lit::Unsigned(b as u64))
}
ast::Lit::Char(lit) => {
let ch = lit.resolve(resolve_context!(cx.q))?;
Ok(hir::Lit::Char(ch))
}
ast::Lit::Str(lit) => {
let string = if cx.in_template {
lit.resolve_template_string(resolve_context!(cx.q))?
} else {
lit.resolve_string(resolve_context!(cx.q))?
};
Ok(hir::Lit::Str(alloc_str!(string.as_ref())))
}
ast::Lit::ByteStr(lit) => {
let bytes = lit.resolve(resolve_context!(cx.q))?;
Ok(hir::Lit::ByteStr(alloc_bytes!(bytes.as_ref())))
}
}
}
#[instrument_ast(span = ast)]
fn expr_unary<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprUnary,
) -> compile::Result<hir::ExprKind<'hir>> {
alloc_with!(cx, ast);
if let ast::UnOp::BorrowRef { .. } = ast.op {
return Err(compile::Error::new(ast, ErrorKind::UnsupportedRef));
}
let (
ast::UnOp::Neg(..),
ast::Expr::Lit(ast::ExprLit {
lit: ast::Lit::Number(n),
..
}),
) = (ast.op, &*ast.expr)
else {
return Ok(hir::ExprKind::Unary(alloc!(hir::ExprUnary {
op: ast.op,
expr: expr(cx, &ast.expr)?,
})));
};
let number = n.resolve(resolve_context!(cx.q))?;
match (number.value, number.suffix) {
(ast::NumberValue::Float(n), _) => Ok(hir::ExprKind::Lit(hir::Lit::Float(-n))),
(ast::NumberValue::Integer(int), Some(ast::NumberSuffix::Unsigned(_, size))) => {
let Some(n) = int.neg().to_u64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadUnsignedOutOfBounds { size },
));
};
if !size.unsigned_in(n) {
return Err(compile::Error::new(
ast,
ErrorKind::BadUnsignedOutOfBounds { size },
));
}
Ok(hir::ExprKind::Lit(hir::Lit::Unsigned(n)))
}
(ast::NumberValue::Integer(int), Some(ast::NumberSuffix::Signed(_, size))) => {
let Some(n) = int.neg().to_i64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds { size },
));
};
if !size.signed_in(n) {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds { size },
));
}
Ok(hir::ExprKind::Lit(hir::Lit::Signed(n)))
}
(ast::NumberValue::Integer(int), _) => {
let Some(n) = int.neg().to_i64() else {
return Err(compile::Error::new(
ast,
ErrorKind::BadSignedOutOfBounds {
size: NumberSize::S64,
},
));
};
Ok(hir::ExprKind::Lit(hir::Lit::Signed(n)))
}
}
}
#[instrument_ast(span = ast)]
fn expr_block<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprBlock,
) -> compile::Result<hir::ExprKind<'hir>> {
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub(crate) enum ExprBlockKind {
Default,
Async,
Const,
}
alloc_with!(cx, ast);
let kind = match (&ast.async_token, &ast.const_token) {
(Some(..), None) => ExprBlockKind::Async,
(None, Some(..)) => ExprBlockKind::Const,
_ => ExprBlockKind::Default,
};
if let ExprBlockKind::Default = kind {
return Ok(hir::ExprKind::Block(alloc!(block(
cx,
ast.label.as_ref(),
&ast.block
)?)));
}
if cx.const_eval {
let ExprBlockKind::Const = kind else {
return Err(compile::Error::msg(
ast,
"Only constant blocks are supported in this context",
));
};
if let Some(label) = &ast.label {
return Err(compile::Error::msg(
label,
"Constant blocks cannot be labelled",
));
};
return Ok(hir::ExprKind::Block(alloc!(block(cx, None, &ast.block)?)));
};
let item =
cx.q.item_for("lowering block", ast.block.id)
.with_span(&ast.block)?;
let meta = cx.lookup_meta(ast, item.item, GenericsParameters::default())?;
match (kind, &meta.kind) {
(ExprBlockKind::Async, &meta::Kind::AsyncBlock { call, do_move, .. }) => {
tracing::trace!("queuing async block build entry");
if let Some(label) = &ast.label {
return Err(compile::Error::msg(
label,
"Async blocks cannot be labelled",
));
};
cx.scopes.push_captures()?;
let block = alloc!(block(cx, None, &ast.block)?);
let layer = cx.scopes.pop().with_span(&ast.block)?;
cx.q.set_used(&meta.item_meta)?;
let captures = &*iter!(layer.captures().map(|(_, id)| id));
let Some(queue) = cx.secondary_builds.as_mut() else {
return Err(compile::Error::new(ast, ErrorKind::AsyncBlockInConst));
};
queue.try_push(SecondaryBuildEntry {
item_meta: meta.item_meta,
build: SecondaryBuild::AsyncBlock(AsyncBlock {
hir: alloc!(hir::AsyncBlock { block, captures }),
call,
}),
})?;
Ok(hir::ExprKind::AsyncBlock(alloc!(hir::ExprAsyncBlock {
hash: meta.hash,
do_move,
captures,
})))
}
(ExprBlockKind::Const, meta::Kind::Const { .. }) => Ok(hir::ExprKind::Const(meta.hash)),
_ => Err(compile::Error::expected_meta(
ast,
meta.info(cx.q.pool)?,
"async or const block",
)),
}
}
fn expr_break<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprBreak,
) -> compile::Result<hir::ExprBreak<'hir>> {
alloc_with!(cx, ast);
let label = match &ast.label {
Some(label) => Some(label.resolve(resolve_context!(cx.q))?),
None => None,
};
let Some(drop) = cx.scopes.loop_drop(label)? else {
if let Some(label) = label {
return Err(compile::Error::new(
ast,
ErrorKind::MissingLabel {
label: label.try_into()?,
},
));
} else {
return Err(compile::Error::new(ast, ErrorKind::BreakUnsupported));
}
};
Ok(hir::ExprBreak {
label: match label {
Some(label) => Some(alloc_str!(label)),
None => None,
},
expr: match &ast.expr {
Some(ast) => Some(alloc!(expr(cx, ast)?)),
None => None,
},
drop: iter!(drop),
})
}
fn expr_continue<'hir>(
cx: &Ctxt<'hir, '_, '_>,
ast: &ast::ExprContinue,
) -> compile::Result<hir::ExprContinue<'hir>> {
alloc_with!(cx, ast);
let label = match &ast.label {
Some(label) => Some(label.resolve(resolve_context!(cx.q))?),
None => None,
};
let Some(drop) = cx.scopes.loop_drop(label)? else {
if let Some(label) = label {
return Err(compile::Error::new(
ast,
ErrorKind::MissingLabel {
label: label.try_into()?,
},
));
} else {
return Err(compile::Error::new(ast, ErrorKind::ContinueUnsupported));
}
};
Ok(hir::ExprContinue {
label: match label {
Some(label) => Some(alloc_str!(label)),
None => None,
},
drop: iter!(drop),
})
}
fn fn_arg<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::FnArg,
) -> compile::Result<hir::FnArg<'hir>> {
alloc_with!(cx, ast);
Ok(match ast {
ast::FnArg::SelfValue(ast) => {
let id = cx.scopes.define(hir::Name::SelfValue, ast)?;
hir::FnArg::SelfValue(ast.span(), id)
}
ast::FnArg::Pat(ast) => hir::FnArg::Pat(alloc!(pat_binding(cx, ast)?)),
})
}
fn local<'hir>(cx: &mut Ctxt<'hir, '_, '_>, ast: &ast::Local) -> compile::Result<hir::Local<'hir>> {
let expr = expr(cx, &ast.expr)?;
let pat = pat_binding(cx, &ast.pat)?;
Ok(hir::Local {
span: ast.span(),
pat,
expr,
})
}
fn unpack_locals(cx: &mut Ctxt<'_, '_, '_>, p: &ast::Pat, e: &ast::Expr) -> compile::Result<bool> {
alloc_with!(cx, p);
match (p, e) {
(p @ ast::Pat::Path(inner), e) => {
let Some(ast::PathKind::Ident(..)) = inner.path.as_kind() else {
return Ok(false);
};
let e = expr(cx, e)?;
let p = pat_binding(cx, p)?;
cx.statement_buffer
.try_push(hir::Stmt::Local(alloc!(hir::Local {
span: p.span().join(e.span()),
pat: p,
expr: e,
})))?;
return Ok(true);
}
(ast::Pat::Tuple(p), ast::Expr::Tuple(e)) => {
if p.items.len() != e.items.len() {
return Ok(false);
}
for ((_, _), (p, _)) in e.items.iter().zip(&p.items) {
if matches!(p, ast::Pat::Rest(..)) {
return Ok(false);
}
}
let mut exprs = Vec::new();
for (e, _) in &e.items {
exprs.try_push(expr(cx, e)?)?;
}
for (e, (p, _)) in exprs.into_iter().zip(&p.items) {
let p = pat_binding(cx, p)?;
cx.statement_buffer
.try_push(hir::Stmt::Local(alloc!(hir::Local {
span: p.span().join(e.span()),
pat: p,
expr: e,
})))?;
}
return Ok(true);
}
_ => {}
};
Ok(false)
}
fn pat_binding<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::Pat,
) -> compile::Result<hir::PatBinding<'hir>> {
alloc_with!(cx, ast);
let pat = pat(cx, ast)?;
let names = iter!(cx.pattern_bindings.drain(..));
Ok(hir::PatBinding { pat, names })
}
fn pat<'hir>(cx: &mut Ctxt<'hir, '_, '_>, ast: &ast::Pat) -> compile::Result<hir::Pat<'hir>> {
fn filter((ast, _): &(ast::Pat, Option<ast::Comma>)) -> Option<&ast::Pat> {
if matches!(ast, ast::Pat::Binding(..) | ast::Pat::Rest(..)) {
return None;
}
Some(ast)
}
alloc_with!(cx, ast);
let kind = {
match ast {
ast::Pat::Ignore(..) => hir::PatKind::Ignore,
ast::Pat::Path(ast) => {
let named = cx.q.convert_path(&ast.path)?;
let parameters = generics_parameters(cx, &named)?;
let path = 'path: {
if let Some(meta) = cx.try_lookup_meta(&ast, named.item, ¶meters)? {
match meta.kind {
meta::Kind::Const => {
let Some(const_value) = cx.q.get_const_value(meta.hash) else {
return Err(compile::Error::msg(
ast,
try_format!("Missing constant for hash {}", meta.hash),
));
};
let const_value = const_value.try_clone().with_span(ast)?;
return pat_const_value(cx, &const_value, ast);
}
_ => {
if let Some((0, kind)) = tuple_match_for(cx, &meta) {
break 'path hir::PatPathKind::Kind(alloc!(kind));
}
}
}
};
if let Some(ident) = ast.path.try_as_ident() {
let name = alloc_str!(ident.resolve(resolve_context!(cx.q))?);
let name = cx.scopes.define(hir::Name::Str(name), ast)?;
cx.pattern_bindings.try_push(name)?;
break 'path hir::PatPathKind::Ident(name);
}
return Err(compile::Error::new(ast, ErrorKind::UnsupportedBinding));
};
hir::PatKind::Path(alloc!(path))
}
ast::Pat::Lit(ast) => hir::PatKind::Lit(alloc!(expr(cx, &ast.expr)?)),
ast::Pat::Vec(ast) => {
let (is_open, count) = pat_items_count(ast.items.as_slice())?;
let items = iter!(
ast.items.iter().filter_map(filter),
ast.items.len(),
|ast| pat(cx, ast)?
);
hir::PatKind::Sequence(alloc!(hir::PatSequence {
kind: hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Vec,
count,
is_open
},
items,
}))
}
ast::Pat::Tuple(ast) => {
let (is_open, count) = pat_items_count(ast.items.as_slice())?;
let items = iter!(
ast.items.iter().filter_map(filter),
ast.items.len(),
|ast| pat(cx, ast)?
);
let kind = if let Some(path) = &ast.path {
let named = cx.q.convert_path(path)?;
let parameters = generics_parameters(cx, &named)?;
let meta = cx.lookup_meta(path, named.item, parameters)?;
let Some((args, kind)) = tuple_match_for(cx, &meta) else {
return Err(compile::Error::expected_meta(
path,
meta.info(cx.q.pool)?,
"type that can be used in a tuple pattern",
));
};
if !(args == count || count < args && is_open) {
return Err(compile::Error::new(
path,
ErrorKind::BadArgumentCount {
expected: args,
actual: count,
},
));
}
kind
} else {
hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Tuple,
count,
is_open,
}
};
hir::PatKind::Sequence(alloc!(hir::PatSequence { kind, items }))
}
ast::Pat::Object(ast) => {
let (is_open, count) = pat_items_count(ast.items.as_slice())?;
let mut keys_dup = HashMap::new();
let bindings = iter!(ast.items.iter().take(count), |(pat, _)| {
let (key, binding) = match pat {
ast::Pat::Binding(binding) => {
let (span, key) = object_key(cx, &binding.key)?;
(
key,
hir::Binding::Binding(
span.span(),
key,
alloc!(self::pat(cx, &binding.pat)?),
),
)
}
ast::Pat::Path(path) => {
let Some(ident) = path.path.try_as_ident() else {
return Err(compile::Error::new(
path,
ErrorKind::UnsupportedPatternExpr,
));
};
let key = alloc_str!(ident.resolve(resolve_context!(cx.q))?);
let id = cx.scopes.define(hir::Name::Str(key), ident)?;
cx.pattern_bindings.try_push(id)?;
(key, hir::Binding::Ident(path.span(), key, id))
}
_ => {
return Err(compile::Error::new(
pat,
ErrorKind::UnsupportedPatternExpr,
));
}
};
if let Some(_existing) = keys_dup.try_insert(key, pat)? {
return Err(compile::Error::new(
pat,
ErrorKind::DuplicateObjectKey {
#[cfg(feature = "emit")]
existing: _existing.span(),
#[cfg(feature = "emit")]
object: pat.span(),
},
));
}
binding
});
let kind = match &ast.ident {
ast::ObjectIdent::Named(path) => {
let named = cx.q.convert_path(path)?;
let parameters = generics_parameters(cx, &named)?;
let meta = cx.lookup_meta(path, named.item, parameters)?;
let Some((mut fields, kind)) =
struct_match_for(cx, &meta, is_open && count == 0)?
else {
return Err(compile::Error::expected_meta(
path,
meta.info(cx.q.pool)?,
"type that can be used in a struct pattern",
));
};
for binding in bindings.iter() {
if !fields.remove(binding.key()) {
return Err(compile::Error::new(
ast,
ErrorKind::LitObjectNotField {
field: binding.key().try_into()?,
item: cx.q.pool.item(meta.item_meta.item).try_to_owned()?,
},
));
}
}
if !is_open && !fields.is_empty() {
let mut fields = fields.into_iter().try_collect::<Box<[_]>>()?;
fields.sort();
return Err(compile::Error::new(
ast,
ErrorKind::PatternMissingFields {
item: cx.q.pool.item(meta.item_meta.item).try_to_owned()?,
#[cfg(feature = "emit")]
fields,
},
));
}
kind
}
ast::ObjectIdent::Anonymous(..) => hir::PatSequenceKind::Anonymous {
type_check: TypeCheck::Object,
count,
is_open,
},
};
hir::PatKind::Object(alloc!(hir::PatObject { kind, bindings }))
}
_ => {
return Err(compile::Error::new(ast, ErrorKind::UnsupportedPatternExpr));
}
}
};
Ok(hir::Pat {
span: ast.span(),
kind,
})
}
fn object_key<'hir, 'ast>(
cx: &Ctxt<'hir, '_, '_>,
ast: &'ast ast::ObjectKey,
) -> compile::Result<(&'ast dyn Spanned, &'hir str)> {
alloc_with!(cx, ast);
Ok(match ast {
ast::ObjectKey::LitStr(lit) => {
let string = lit.resolve(resolve_context!(cx.q))?;
(lit, alloc_str!(string.as_ref()))
}
ast::ObjectKey::Path(ast) => {
let Some(ident) = ast.try_as_ident() else {
return Err(compile::Error::expected(ast, "object key"));
};
let string = ident.resolve(resolve_context!(cx.q))?;
(ident, alloc_str!(string))
}
})
}
#[instrument_ast(span = ast)]
fn expr_path<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::Path,
in_path: bool,
) -> compile::Result<hir::ExprKind<'hir>> {
alloc_with!(cx, ast);
if let Some(ast::PathKind::SelfValue) = ast.as_kind() {
let Some((id, _)) = cx.scopes.get(hir::Name::SelfValue)? else {
return Err(compile::Error::new(ast, ErrorKind::MissingSelf));
};
return Ok(hir::ExprKind::Variable(id));
}
if let Needs::Value = cx.needs {
if let Some(name) = ast.try_as_ident() {
let name = alloc_str!(name.resolve(resolve_context!(cx.q))?);
if let Some((name, _)) = cx.scopes.get(hir::Name::Str(name))? {
return Ok(hir::ExprKind::Variable(name));
}
}
}
if in_path {
return Ok(hir::ExprKind::Path);
}
let named = cx.q.convert_path(ast)?;
let parameters = generics_parameters(cx, &named)?;
if let Some(meta) = cx.try_lookup_meta(ast, named.item, ¶meters)? {
return expr_path_meta(cx, &meta, ast);
}
if let (Needs::Value, Some(local)) = (cx.needs, ast.try_as_ident()) {
let local = local.resolve(resolve_context!(cx.q))?;
if !local.starts_with(char::is_uppercase) {
return Err(compile::Error::new(
ast,
ErrorKind::MissingLocal {
name: Box::<str>::try_from(local)?,
},
));
}
}
let kind = if !parameters.parameters.is_empty() {
ErrorKind::MissingItemParameters {
item: cx.q.pool.item(named.item).try_to_owned()?,
parameters: parameters.parameters,
}
} else {
ErrorKind::MissingItem {
item: cx.q.pool.item(named.item).try_to_owned()?,
}
};
Err(compile::Error::new(ast, kind))
}
#[instrument_ast(span = span)]
fn expr_path_meta<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
meta: &meta::Meta,
span: &dyn Spanned,
) -> compile::Result<hir::ExprKind<'hir>> {
alloc_with!(cx, span);
if let Needs::Value = cx.needs {
match &meta.kind {
meta::Kind::Struct {
fields: meta::Fields::Empty,
..
}
| meta::Kind::Variant {
fields: meta::Fields::Empty,
..
} => Ok(hir::ExprKind::Call(alloc!(hir::ExprCall {
call: hir::Call::Meta { hash: meta.hash },
args: &[],
}))),
meta::Kind::Variant {
fields: meta::Fields::Unnamed(0),
..
}
| meta::Kind::Struct {
fields: meta::Fields::Unnamed(0),
..
} => Ok(hir::ExprKind::Call(alloc!(hir::ExprCall {
call: hir::Call::Meta { hash: meta.hash },
args: &[],
}))),
meta::Kind::Struct {
fields: meta::Fields::Unnamed(..),
..
} => Ok(hir::ExprKind::Fn(meta.hash)),
meta::Kind::Variant {
fields: meta::Fields::Unnamed(..),
..
} => Ok(hir::ExprKind::Fn(meta.hash)),
meta::Kind::Function { .. } => Ok(hir::ExprKind::Fn(meta.hash)),
meta::Kind::Const { .. } => Ok(hir::ExprKind::Const(meta.hash)),
meta::Kind::Struct { .. } | meta::Kind::Type { .. } | meta::Kind::Enum { .. } => {
Ok(hir::ExprKind::Type(Type::new(meta.hash)))
}
_ => Err(compile::Error::expected_meta(
span,
meta.info(cx.q.pool)?,
"something that can be used as a value",
)),
}
} else {
let Some(type_hash) = meta.type_hash_of() else {
return Err(compile::Error::expected_meta(
span,
meta.info(cx.q.pool)?,
"something that has a type",
));
};
Ok(hir::ExprKind::Type(Type::new(type_hash)))
}
}
fn condition<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::Condition,
) -> compile::Result<hir::Condition<'hir>> {
alloc_with!(cx, ast);
Ok(match ast {
ast::Condition::Expr(ast) => hir::Condition::Expr(alloc!(expr(cx, ast)?)),
ast::Condition::ExprLet(ast) => hir::Condition::ExprLet(alloc!(hir::ExprLet {
pat: pat_binding(cx, &ast.pat)?,
expr: expr(cx, &ast.expr)?,
})),
})
}
fn pat_items_count(items: &[(ast::Pat, Option<ast::Comma>)]) -> compile::Result<(bool, usize)> {
let mut it = items.iter();
let (is_open, mut count) = match it.next_back() {
Some((pat, _)) => matches!(pat, ast::Pat::Rest { .. })
.then(|| (true, 0))
.unwrap_or((false, 1)),
None => return Ok((false, 0)),
};
for (pat, _) in it {
if let ast::Pat::Rest { .. } = pat {
return Err(compile::Error::new(pat, ErrorKind::UnsupportedPatternRest));
}
count += 1;
}
Ok((is_open, count))
}
fn struct_match_for(
cx: &Ctxt<'_, '_, '_>,
meta: &meta::Meta,
open: bool,
) -> alloc::Result<Option<(HashSet<Box<str>>, hir::PatSequenceKind)>> {
let (fields, kind) = match &meta.kind {
meta::Kind::Struct { fields, .. } => {
(fields, hir::PatSequenceKind::Type { hash: meta.hash })
}
meta::Kind::Variant {
enum_hash,
index,
fields,
..
} => {
let kind = if let Some(type_check) = cx.q.context.type_check_for(meta.hash) {
hir::PatSequenceKind::BuiltInVariant { type_check }
} else {
hir::PatSequenceKind::Variant {
variant_hash: meta.hash,
enum_hash: *enum_hash,
index: *index,
}
};
(fields, kind)
}
_ => {
return Ok(None);
}
};
let fields = match fields {
meta::Fields::Unnamed(0) if open => HashSet::new(),
meta::Fields::Empty if open => HashSet::new(),
meta::Fields::Named(st) => st
.fields
.iter()
.map(|f| f.name.try_clone())
.try_collect::<alloc::Result<_>>()??,
_ => return Ok(None),
};
Ok(Some((fields, kind)))
}
fn tuple_match_for(
cx: &Ctxt<'_, '_, '_>,
meta: &meta::Meta,
) -> Option<(usize, hir::PatSequenceKind)> {
Some(match &meta.kind {
meta::Kind::Struct {
fields: meta::Fields::Empty,
..
} => (0, hir::PatSequenceKind::Type { hash: meta.hash }),
meta::Kind::Struct {
fields: meta::Fields::Unnamed(args),
..
} => (*args, hir::PatSequenceKind::Type { hash: meta.hash }),
meta::Kind::Variant {
enum_hash,
index,
fields,
..
} => {
let args = match fields {
meta::Fields::Unnamed(args) => *args,
meta::Fields::Empty => 0,
_ => return None,
};
let kind = if let Some(type_check) = cx.q.context.type_check_for(meta.hash) {
hir::PatSequenceKind::BuiltInVariant { type_check }
} else {
hir::PatSequenceKind::Variant {
variant_hash: meta.hash,
enum_hash: *enum_hash,
index: *index,
}
};
(args, kind)
}
_ => return None,
})
}
fn generics_parameters(
cx: &mut Ctxt<'_, '_, '_>,
named: &Named<'_>,
) -> compile::Result<GenericsParameters> {
let mut parameters = GenericsParameters {
trailing: named.trailing,
parameters: [None, None],
};
for (value, o) in named
.parameters
.iter()
.zip(parameters.parameters.iter_mut())
{
if let &Some((span, generics)) = value {
let mut builder = ParametersBuilder::new();
for (s, _) in generics {
let hir::ExprKind::Type(ty) = expr(cx, &s.expr)?.kind else {
return Err(compile::Error::new(s, ErrorKind::UnsupportedGenerics));
};
builder = builder.add(ty.into_hash()).with_span(span)?;
}
*o = Some(builder.finish());
}
}
Ok(parameters)
}
#[instrument_ast(span = ast)]
fn expr_call<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprCall,
) -> compile::Result<hir::ExprCall<'hir>> {
fn find_path(ast: &ast::Expr) -> Option<&ast::Path> {
let mut current = ast;
loop {
match current {
ast::Expr::Path(path) => return Some(path),
ast::Expr::Empty(ast) => {
current = &*ast.expr;
continue;
}
_ => return None,
}
}
}
alloc_with!(cx, ast);
let in_path = replace(&mut cx.in_path, true);
let expr = expr(cx, &ast.expr)?;
cx.in_path = in_path;
let call = 'ok: {
match expr.kind {
hir::ExprKind::Variable(name) => {
break 'ok hir::Call::Var { name };
}
hir::ExprKind::Path => {
let Some(path) = find_path(&ast.expr) else {
return Err(compile::Error::msg(&ast.expr, "Expected path"));
};
let named = cx.q.convert_path(path)?;
let parameters = generics_parameters(cx, &named)?;
let meta = cx.lookup_meta(path, named.item, parameters)?;
debug_assert_eq!(meta.item_meta.item, named.item);
match &meta.kind {
meta::Kind::Struct {
fields: meta::Fields::Empty,
..
}
| meta::Kind::Variant {
fields: meta::Fields::Empty,
..
} => {
if !ast.args.is_empty() {
return Err(compile::Error::new(
&ast.args,
ErrorKind::BadArgumentCount {
expected: 0,
actual: ast.args.len(),
},
));
}
}
meta::Kind::Struct {
fields: meta::Fields::Unnamed(args),
..
}
| meta::Kind::Variant {
fields: meta::Fields::Unnamed(args),
..
} => {
if *args != ast.args.len() {
return Err(compile::Error::new(
&ast.args,
ErrorKind::BadArgumentCount {
expected: *args,
actual: ast.args.len(),
},
));
}
if *args == 0 {
cx.q.diagnostics.remove_tuple_call_parens(
cx.source_id,
&ast.args,
path,
None,
)?;
}
}
meta::Kind::Function { .. } => {
if let Some(message) = cx.q.lookup_deprecation(meta.hash) {
cx.q.diagnostics.used_deprecated(
cx.source_id,
&expr.span,
None,
message.try_into()?,
)?;
};
}
meta::Kind::ConstFn => {
let from =
cx.q.item_for("lowering constant function", ast.id)
.with_span(ast)?;
break 'ok hir::Call::ConstFn {
from_module: from.module,
from_item: from.item,
id: meta.item_meta.item,
};
}
_ => {
return Err(compile::Error::expected_meta(
ast,
meta.info(cx.q.pool)?,
"something that can be called as a function",
));
}
};
break 'ok hir::Call::Meta { hash: meta.hash };
}
hir::ExprKind::FieldAccess(&hir::ExprFieldAccess {
expr_field,
expr: target,
}) => {
let hash = match expr_field {
hir::ExprField::Index(index) => Hash::index(index),
hir::ExprField::Ident(ident) => {
cx.q.unit.insert_debug_ident(ident)?;
Hash::ident(ident)
}
hir::ExprField::IdentGenerics(ident, hash) => {
cx.q.unit.insert_debug_ident(ident)?;
Hash::ident(ident).with_function_parameters(hash)
}
};
break 'ok hir::Call::Associated {
target: alloc!(target),
hash,
};
}
_ => {}
}
break 'ok hir::Call::Expr { expr: alloc!(expr) };
};
Ok(hir::ExprCall {
call,
args: iter!(&ast.args, |(ast, _)| self::expr(cx, ast)?),
})
}
#[instrument_ast(span = ast)]
fn expr_field_access<'hir>(
cx: &mut Ctxt<'hir, '_, '_>,
ast: &ast::ExprFieldAccess,
) -> compile::Result<hir::ExprFieldAccess<'hir>> {
alloc_with!(cx, ast);
let expr_field = match &ast.expr_field {
ast::ExprField::LitNumber(ast) => {
let number = ast.resolve(resolve_context!(cx.q))?;
let Some(index) = number.as_tuple_index() else {
return Err(compile::Error::new(
ast,
ErrorKind::UnsupportedTupleIndex { number },
));
};
hir::ExprField::Index(index)
}
ast::ExprField::Path(ast) => {
let Some((ident, generics)) = ast.try_as_ident_generics() else {
return Err(compile::Error::new(ast, ErrorKind::BadFieldAccess));
};
let ident = alloc_str!(ident.resolve(resolve_context!(cx.q))?);
match generics {
Some(generics) => {
let mut builder = ParametersBuilder::new();
for (s, _) in generics {
let hir::ExprKind::Type(ty) = expr(cx, &s.expr)?.kind else {
return Err(compile::Error::new(s, ErrorKind::UnsupportedGenerics));
};
builder = builder.add(ty.into_hash()).with_span(s)?;
}
hir::ExprField::IdentGenerics(ident, builder.finish())
}
None => hir::ExprField::Ident(ident),
}
}
};
Ok(hir::ExprFieldAccess {
expr: expr(cx, &ast.expr)?,
expr_field,
})
}