mod attributes;
pub mod builtins;
pub mod error;
pub mod global_symbols;
mod impls;
mod lambda;
pub mod pipelines;
pub mod testutil;
mod type_level_if;
pub mod types;
use attributes::LocAttributeExt;
use global_symbols::visit_meta_type;
use impls::visit_impl;
use lambda::visit_lambda;
use num::{BigInt, Zero};
use pipelines::PipelineContext;
use recursive::recursive;
use spade_diagnostics::codespan::Span;
use spade_diagnostics::diag_list::{DiagList, ResultExt};
use spade_diagnostics::diagnostic::SuggestionParts;
use spade_diagnostics::{diag_bail, Diagnostic};
use spade_hir::expression::Safety;
use spade_types::meta_types::MetaType;
use tracing::{event, Level};
use type_level_if::expand_type_level_if;
use crate::attributes::AttributeListExt;
pub use crate::impls::ensure_unique_anonymous_traits;
use crate::pipelines::maybe_perform_pipelining_tasks;
use crate::types::{IsInOut, IsPort, IsSelf};
use ast::{Binding, CallKind, ParameterList};
use hir::expression::{BinaryOperator, IntLiteralKind};
use hir::param_util::ArgumentError;
use hir::symbol_table::DeclarationState;
use hir::symbol_table::{LookupError, SymbolTable, Thing, TypeSymbol};
use hir::{ConstGeneric, ExecutableItem, PatternKind, TraitName, WalTrace};
use spade_ast::{self as ast, Attribute, Expression, TypeParam, WhereClause};
pub use spade_common::id_tracker;
use spade_common::id_tracker::{ExprIdTracker, ImplIdTracker};
use spade_common::location_info::{FullSpan, Loc, WithLocation};
use spade_common::name::{Identifier, Path};
use spade_hir::{self as hir, ExprKind, ItemList, Module, TraitSpec, TypeExpression, TypeSpec};
use std::collections::HashSet;
use error::Result;
#[derive(Debug)]
pub struct Context {
pub symtab: SymbolTable,
pub item_list: ItemList,
pub idtracker: ExprIdTracker,
pub impl_idtracker: ImplIdTracker,
pub pipeline_ctx: Option<PipelineContext>,
pub self_ctx: SelfContext,
pub current_unit: Option<hir::UnitHead>,
pub diags: DiagList,
pub safety: Safety,
}
impl Context {
fn in_fresh_unit<T>(&mut self, transform: impl FnOnce(&mut Context) -> T) -> T {
let mut tmp_pipeline_ctx = None;
let mut tmp_self_ctx = SelfContext::FreeStanding;
let mut tmp_current_unit = None;
{
let Context {
symtab: _,
item_list: _,
idtracker: _,
impl_idtracker: _,
pipeline_ctx,
self_ctx,
current_unit,
diags: _,
safety: _,
} = self;
std::mem::swap(pipeline_ctx, &mut tmp_pipeline_ctx);
std::mem::swap(self_ctx, &mut tmp_self_ctx);
std::mem::swap(current_unit, &mut tmp_current_unit);
}
let result = transform(self);
{
let Context {
symtab: _,
item_list: _,
idtracker: _,
impl_idtracker: _,
pipeline_ctx,
self_ctx,
current_unit,
diags: _,
safety: _,
} = self;
std::mem::swap(pipeline_ctx, &mut tmp_pipeline_ctx);
std::mem::swap(self_ctx, &mut tmp_self_ctx);
std::mem::swap(current_unit, &mut tmp_current_unit);
}
result
}
}
trait LocExt<T> {
fn try_visit<V, U>(&self, visitor: V, context: &mut Context) -> Result<Loc<U>>
where
V: Fn(&T, &mut Context) -> Result<U>;
fn visit<V, U>(&self, visitor: V, context: &mut Context) -> Loc<U>
where
V: Fn(&T, &mut Context) -> U;
}
impl<T> LocExt<T> for Loc<T> {
fn try_visit<V, U>(&self, visitor: V, context: &mut Context) -> Result<Loc<U>>
where
V: Fn(&T, &mut Context) -> Result<U>,
{
self.map_ref(|t| visitor(t, context)).map_err(|e, _| e)
}
fn visit<V, U>(&self, visitor: V, context: &mut Context) -> Loc<U>
where
V: Fn(&T, &mut Context) -> U,
{
self.map_ref(|t| visitor(t, context))
}
}
#[tracing::instrument(skip_all, fields(name=%param.name()))]
pub fn visit_type_param(param: &ast::TypeParam, ctx: &mut Context) -> Result<hir::TypeParam> {
match ¶m {
ast::TypeParam::TypeName {
name: ident,
traits,
} => {
let trait_bounds: Vec<Loc<TraitSpec>> = traits
.iter()
.map(|t| visit_trait_spec(t, &TypeSpecKind::TraitBound, ctx))
.collect::<Result<_>>()?;
let name_id = ctx.symtab.add_type(
Path::ident(ident.clone()),
TypeSymbol::GenericArg {
traits: trait_bounds.clone(),
}
.at_loc(ident),
);
Ok(hir::TypeParam {
ident: ident.clone(),
name_id,
trait_bounds,
meta: MetaType::Type,
})
}
ast::TypeParam::TypeWithMeta { meta, name } => {
let meta = visit_meta_type(meta)?;
let name_id = ctx.symtab.add_type(
Path::ident(name.clone()),
TypeSymbol::GenericMeta(meta.clone()).at_loc(name),
);
Ok(hir::TypeParam {
ident: name.clone(),
name_id,
trait_bounds: vec![],
meta,
})
}
}
}
#[tracing::instrument(skip_all, fields(name=%param.name()))]
pub fn re_visit_type_param(param: &ast::TypeParam, ctx: &Context) -> Result<hir::TypeParam> {
match ¶m {
ast::TypeParam::TypeName {
name: ident,
traits: _,
} => {
let path = Path::ident(ident.clone()).at_loc(ident);
let (name_id, tsym) = ctx.symtab.lookup_type_symbol(&path)?;
let trait_bounds = match &tsym.inner {
TypeSymbol::GenericArg { traits } => traits.clone(),
_ => return Err(Diagnostic::bug(
ident,
format!(
"Trait bound on {ident} on non-generic argument, which should've been caught by the first pass"
),
))
};
Ok(hir::TypeParam {
ident: ident.clone(),
name_id,
trait_bounds,
meta: MetaType::Type,
})
}
ast::TypeParam::TypeWithMeta { meta, name } => {
let path = Path::ident(name.clone()).at_loc(name);
let name_id = ctx.symtab.lookup_type_symbol(&path)?.0;
Ok(hir::TypeParam {
ident: name.clone(),
name_id,
trait_bounds: vec![],
meta: visit_meta_type(meta)?,
})
}
}
}
pub enum TypeSpecKind {
Argument,
OutputType,
EnumMember,
StructMember,
ImplTrait,
ImplTarget,
BindingType,
Turbofish,
PipelineHeadDepth,
PipelineRegCount,
PipelineInstDepth,
TraitBound,
TypeLevelIf,
}
#[recursive]
pub fn visit_type_expression(
expr: &ast::TypeExpression,
kind: &TypeSpecKind,
ctx: &mut Context,
) -> Result<hir::TypeExpression> {
match expr {
ast::TypeExpression::TypeSpec(spec) => {
let inner = visit_type_spec(spec, kind, ctx)?;
Ok(hir::TypeExpression::TypeSpec(inner.inner))
}
ast::TypeExpression::Integer(val) => Ok(hir::TypeExpression::Integer(val.clone())),
ast::TypeExpression::String(val) => Ok(hir::TypeExpression::String(val.clone())),
ast::TypeExpression::ConstGeneric(expr) => {
let default_error = |message, primary| {
Err(Diagnostic::error(
expr.as_ref(),
format!("{message} cannot have const generics in their type"),
)
.primary_label(format!("Const generic in {primary}")))
};
match kind {
TypeSpecKind::ImplTrait => default_error("Implemented traits", "implemented trait"),
TypeSpecKind::ImplTarget => default_error("Impl targets", "impl target"),
TypeSpecKind::EnumMember => default_error("Enum members", "enum member"),
TypeSpecKind::StructMember => default_error("Struct members", "struct member"),
TypeSpecKind::TraitBound => {
default_error("Traits used in trait bounds", "trait bound")
}
TypeSpecKind::Argument
| TypeSpecKind::OutputType
| TypeSpecKind::Turbofish
| TypeSpecKind::TypeLevelIf
| TypeSpecKind::BindingType
| TypeSpecKind::PipelineInstDepth
| TypeSpecKind::PipelineRegCount
| TypeSpecKind::PipelineHeadDepth => {
visit_const_generic(expr.as_ref(), ctx).map(hir::TypeExpression::ConstGeneric)
}
}
}
}
}
pub fn visit_type_spec(
t: &Loc<ast::TypeSpec>,
kind: &TypeSpecKind,
ctx: &mut Context,
) -> Result<Loc<hir::TypeSpec>> {
let trait_loc = if let SelfContext::TraitDefinition(TraitName::Named(name)) = &ctx.self_ctx {
name.loc()
} else {
().nowhere()
};
if matches!(ctx.self_ctx, SelfContext::TraitDefinition(_)) && t.is_self()? {
return Ok(hir::TypeSpec::TraitSelf(().at_loc(&trait_loc)).at_loc(t));
};
let result = match &t.inner {
ast::TypeSpec::Named(path, params) => {
let (base_id, base_t) = ctx.symtab.lookup_type_symbol(path)?;
match &base_t.inner {
TypeSymbol::Declared(generic_args, _) => {
let visited_params = params
.as_ref()
.map(|o| &o.inner)
.into_iter()
.flatten()
.map(|p| p.try_map_ref(|p| visit_type_expression(p, kind, ctx)))
.collect::<Result<Vec<_>>>()?;
if generic_args.len() != visited_params.len() {
Err(Diagnostic::error(
params
.as_ref()
.map(|p| ().at_loc(p))
.unwrap_or(().at_loc(t)),
"Wrong number of generic type parameters",
)
.primary_label(format!(
"Expected {} type parameter{}",
generic_args.len(),
if generic_args.len() != 1 { "s" } else { "" }
))
.secondary_label(
if !generic_args.is_empty() {
().between_locs(
&generic_args[0],
&generic_args[generic_args.len() - 1],
)
} else {
().at_loc(&base_t)
},
format!(
"Because this has {} type parameter{}",
generic_args.len(),
if generic_args.len() != 1 { "s" } else { "" }
),
))
} else {
Ok(hir::TypeSpec::Declared(
base_id.at_loc(path),
visited_params,
))
}
}
TypeSymbol::GenericArg { traits: _ } | TypeSymbol::GenericMeta(_) => {
if let Some(params) = params {
Err(
Diagnostic::error(params, "Generic arguments given for a generic type")
.primary_label("Generic arguments not allowed here")
.secondary_label(base_t, format!("{path} is a generic type")),
)
} else {
Ok(hir::TypeSpec::Generic(base_id.at_loc(path)))
}
}
TypeSymbol::Alias(expr) => match &expr.inner {
TypeExpression::TypeSpec(spec) => Ok(spec.clone()),
TypeExpression::Integer(_) | TypeExpression::ConstGeneric(_) | TypeExpression::String(_) => {
Err(Diagnostic::error(
t,
"Type aliases to integers, strings and const generics are currently unsupported",
)
.primary_label("Alias to non-type")
.secondary_label(expr, "Type alias points here"))
}
},
}
}
ast::TypeSpec::Array { inner, size } => {
let inner = match visit_type_expression(inner, kind, ctx)? {
hir::TypeExpression::TypeSpec(t) => Box::new(t.at_loc(inner)),
_ => {
return Err(Diagnostic::error(
inner.as_ref(),
"Arrays elements must be types, not type level integers",
)
.primary_label("Non-type array element"))
}
};
let size = Box::new(visit_type_expression(size, kind, ctx)?.at_loc(size));
Ok(hir::TypeSpec::Array { inner, size })
}
ast::TypeSpec::Tuple(inner) => {
let inner = inner
.iter()
.map(|p| match visit_type_expression(p, kind, ctx)? {
hir::TypeExpression::TypeSpec(t) => match &t {
TypeSpec::Tuple(_)
| TypeSpec::Array { inner: _, size: _ }
| TypeSpec::Inverted(_)
| TypeSpec::Wire(_)
| TypeSpec::TraitSelf(_)
| TypeSpec::Wildcard(_)
| TypeSpec::Declared(_, _) => Ok(t.at_loc(p)),
TypeSpec::Generic(name) => {
let inner = ctx.symtab.type_symbol_by_id(&name.inner);
match &inner.inner {
TypeSymbol::Declared(_, _)
| TypeSymbol::GenericArg { traits: _ }
| TypeSymbol::GenericMeta(MetaType::Type) => Ok(t.at_loc(p)),
| TypeSymbol::GenericMeta(other_meta) => {
return Err(Diagnostic::error(name, format!("Tuple members can only be types, found {other_meta}"))
.primary_label(format!("Expected type, found {other_meta}"))
.secondary_label(&inner, format!("{name} is defined as {other_meta} here")))
}
TypeSymbol::Alias(_) => {
return Err(Diagnostic::bug(p, "Aliases in tuple types are currently unsupported"));
},
}
}
},
_ => {
return Err(Diagnostic::error(
p,
"Tuple elements must be types, not type level integers",
)
.primary_label("Tuples cannot contain non-types"))
}
})
.collect::<Result<Vec<_>>>()?;
let transitive_port_witness = inner
.iter()
.map(|p| {
if p.is_port(ctx)? {
Ok(Some(p))
} else {
Ok(None)
}
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.find_map(|x| x);
if let Some(witness) = transitive_port_witness {
for ty in &inner {
if !ty.is_port(ctx)? {
return Err(Diagnostic::error(
ty,
"Cannot mix ports and non-ports in a tuple",
)
.primary_label("This is not a port")
.secondary_label(witness, "This is a port")
.note("A tuple must either contain only ports or no ports"));
}
}
}
Ok(hir::TypeSpec::Tuple(inner))
}
ast::TypeSpec::Wire(inner) => {
let inner = match visit_type_expression(inner, kind, ctx)? {
hir::TypeExpression::TypeSpec(t) => t.at_loc(inner),
_ => {
return Err(Diagnostic::error(
inner.as_ref(),
"Wire inner types must be types, not type level integers",
)
.primary_label("Wires cannot contain non-types"))
}
};
if inner.is_port(&ctx)? {
return Err(Diagnostic::from(error::WireOfPort {
full_type: t.loc(),
inner_type: inner.loc(),
}));
}
if inner.is_inout(&ctx)? {
return Err(Diagnostic::from(error::WireOfInOut {
full_type: t.loc(),
inner_type: inner.loc(),
}));
}
Ok(hir::TypeSpec::Wire(Box::new(inner)))
}
ast::TypeSpec::Inverted(inner) => {
let inner = match visit_type_expression(inner, kind, ctx)? {
hir::TypeExpression::TypeSpec(t) => t.at_loc(inner),
_ => {
return Err(Diagnostic::error(
inner.as_ref(),
"Inverted inner types must be types, not type level integers",
)
.primary_label("Non-type cannot be inverted"))
}
};
if !inner.is_port(ctx)? {
Err(Diagnostic::error(t, "A non-port type can not be inverted")
.primary_label("Inverting non-port")
.secondary_label(inner, "This is not a port"))
} else {
Ok(hir::TypeSpec::Inverted(Box::new(inner)))
}
}
ast::TypeSpec::Wildcard => {
let default_error = |message, primary| {
Err(
Diagnostic::error(t, format!("{message} cannot have wildcards in their type"))
.primary_label(format!("Wildcard in {primary}")),
)
};
match kind {
TypeSpecKind::Argument => default_error("Argument types", "argument type"),
TypeSpecKind::OutputType => default_error("Return types", "return type"),
TypeSpecKind::ImplTrait => default_error("Implemented traits", "implemented trait"),
TypeSpecKind::ImplTarget => default_error("Impl targets", "impl target"),
TypeSpecKind::EnumMember => default_error("Enum members", "enum member"),
TypeSpecKind::StructMember => default_error("Struct members", "struct member"),
TypeSpecKind::PipelineHeadDepth => {
default_error("Pipeline depths", "pipeline depth")
}
TypeSpecKind::PipelineRegCount => {
default_error("Register counts", "register count")
}
TypeSpecKind::TraitBound => {
default_error("Traits used in trait bound", "trait bound")
}
TypeSpecKind::PipelineInstDepth
| TypeSpecKind::TypeLevelIf
| TypeSpecKind::Turbofish
| TypeSpecKind::BindingType => Ok(hir::TypeSpec::Wildcard(t.loc())),
}
}
};
Ok(result?.at_loc(t))
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelfContext {
FreeStanding,
ImplBlock(Loc<hir::TypeSpec>),
TraitDefinition(TraitName),
}
fn visit_parameter_list(
l: &Loc<ParameterList>,
ctx: &mut Context,
no_mangle_all: Option<Loc<()>>,
) -> Result<Loc<hir::ParameterList>> {
let mut arg_names: HashSet<Loc<Identifier>> = HashSet::new();
let mut result = vec![];
if let SelfContext::ImplBlock(_) = ctx.self_ctx {
if l.self_.is_none() {
let mut diag = Diagnostic::error(l, "Method must take 'self' as the first parameter")
.primary_label("Missing self");
let suggest_msg = "Consider adding self";
diag = if l.args.is_empty() {
diag.span_suggest_replace(suggest_msg, l, "(self)")
} else {
diag.span_suggest_insert_before(suggest_msg, &l.args[0].1, "self, ")
};
return Err(diag);
}
}
if let Some(self_loc) = l.self_ {
match &ctx.self_ctx {
SelfContext::FreeStanding => {
return Err(Diagnostic::error(
self_loc,
"'self' cannot be used in free standing units",
)
.primary_label("not allowed here"))
}
SelfContext::ImplBlock(spec) => result.push(hir::Parameter {
no_mangle: None,
name: Identifier(String::from("self")).at_loc(&self_loc),
ty: spec.clone(),
field_translator: None,
}),
SelfContext::TraitDefinition(_) => result.push(hir::Parameter {
no_mangle: None,
name: Identifier(String::from("self")).at_loc(&self_loc),
ty: hir::TypeSpec::TraitSelf(self_loc).at_loc(&self_loc),
field_translator: None,
}),
}
}
for (attrs, name, input_type) in &l.args {
if let Some(prev) = arg_names.get(name) {
return Err(
Diagnostic::error(name, "Multiple arguments with the same name")
.primary_label(format!("{name} later declared here"))
.secondary_label(prev, format!("{name} previously declared here")),
);
}
arg_names.insert(name.clone());
let t = visit_type_spec(input_type, &TypeSpecKind::Argument, ctx)?;
let mut attrs = attrs.clone();
let no_mangle = attrs
.consume_no_mangle()
.map(|ident| ident.loc())
.or(no_mangle_all);
let field_translator = attrs.consume_translator();
attrs.report_unused("a parameter")?;
result.push(hir::Parameter {
name: name.clone(),
ty: t,
no_mangle,
field_translator,
});
}
Ok(hir::ParameterList(result).at_loc(l))
}
fn build_no_mangle_all_output_diagnostic(
head: &ast::UnitHead,
output_type: &Loc<TypeSpec>,
body_for_diagnostics: Option<&Loc<Expression>>,
) -> Diagnostic {
let suffix_length = head
.inputs
.args
.iter()
.filter_map(|(_, name, _)| {
if name.0.contains("out") {
Some(name.0.len())
} else {
None
}
})
.max()
.unwrap_or(2)
- 2;
let suggested_name = format!("out{}", "_".repeat(suffix_length));
let output_is_wire = output_type.to_string().starts_with("&");
let suggested_type = format!(
"inv {}{}",
if output_is_wire {
""
} else {
"&"
},
output_type
);
let mut diagnostic = Diagnostic::error(output_type, "Cannot apply `#[no_mangle(all)]`")
.primary_label("Output types are always mangled");
let output_arrow_span = &head.output_type.as_ref().unwrap().0.span;
let output_type_full_span: FullSpan = output_type.into();
let mut first_suggestion = SuggestionParts::new().part(
(
Span::new(output_arrow_span.start(), output_type_full_span.0.end()),
output_type_full_span.1,
),
"",
);
if head.inputs.args.is_empty() {
let (span, file) = (head.inputs.span, head.inputs.file_id);
first_suggestion = first_suggestion.part(
(span, file),
format!("({}: {})", suggested_name, suggested_type),
);
} else {
let last_parameter = &head.inputs.args.last().unwrap().2;
let (span, file) = (last_parameter.span, last_parameter.file_id);
first_suggestion.push_part(
(Span::new(span.end(), span.end()), file),
format!(", {}: {}", suggested_name, suggested_type),
);
}
diagnostic.push_span_suggest_multipart(
"Consider replacing the output with an inverted input",
first_suggestion,
);
if let Some(block) = body_for_diagnostics {
let block = block.assume_block();
if let Some(result) = block.result.as_ref() {
if output_is_wire {
diagnostic = diagnostic.span_suggest_remove("Remember to `set` the inverted input instead of ending the block with an output", result);
} else {
let (span, file) = (result.span, result.file_id);
diagnostic.push_span_suggest_multipart(
"...and `set` the inverted input to the return value",
SuggestionParts::new()
.part(
(Span::new(span.start(), span.start()), file),
format!("set {} = &", suggested_name),
)
.part((Span::new(span.end(), span.end()), file), ";"),
);
}
}
}
diagnostic
}
pub fn visit_unit_kind(kind: &ast::UnitKind, ctx: &mut Context) -> Result<hir::UnitKind> {
let inner = match kind {
ast::UnitKind::Function => hir::UnitKind::Function(hir::FunctionKind::Fn),
ast::UnitKind::Entity => hir::UnitKind::Entity,
ast::UnitKind::Pipeline(depth) => hir::UnitKind::Pipeline {
depth: depth
.try_map_ref(|t| visit_type_expression(t, &TypeSpecKind::PipelineHeadDepth, ctx))?,
depth_typeexpr_id: ctx.idtracker.next(),
},
};
Ok(inner)
}
#[tracing::instrument(skip_all, fields(name=%head.name))]
pub fn unit_head(
head: &ast::UnitHead,
scope_type_params: &Option<Loc<Vec<Loc<TypeParam>>>>,
scope_where_clauses: &[Loc<hir::WhereClause>],
ctx: &mut Context,
body_for_diagnostics: Option<&Loc<Expression>>,
) -> Result<hir::UnitHead> {
ctx.symtab.new_scope();
let scope_type_params = scope_type_params
.as_ref()
.map(Loc::strip_ref)
.into_iter()
.flatten()
.map(|loc| loc.try_map_ref(|p| re_visit_type_param(p, ctx)))
.collect::<Result<Vec<Loc<hir::TypeParam>>>>()?;
let unit_type_params = head
.type_params
.as_ref()
.map(Loc::strip_ref)
.into_iter()
.flatten()
.map(|loc| loc.try_map_ref(|p| visit_type_param(p, ctx)))
.collect::<Result<Vec<Loc<hir::TypeParam>>>>()?;
let unit_where_clauses = visit_where_clauses(&head.where_clauses, ctx);
let output_type = if let Some(output_type) = &head.output_type {
Some(visit_type_spec(
&output_type.1,
&TypeSpecKind::OutputType,
ctx,
)?)
} else {
None
};
let no_mangle_all = head
.attributes
.0
.iter()
.find(|attribute| matches!(attribute.inner, Attribute::NoMangle { all: true }))
.map(|attribute| ().at_loc(attribute));
if no_mangle_all.is_some()
&& output_type
.as_ref()
.map(|output_type| {
!(matches!(&**output_type, TypeSpec::Tuple(inner) if inner.is_empty()))
})
.unwrap_or(false)
{
return Err(build_no_mangle_all_output_diagnostic(
head,
output_type.as_ref().unwrap(),
body_for_diagnostics,
));
}
let inputs = visit_parameter_list(&head.inputs, ctx, no_mangle_all)?;
let unit_kind: Result<_> = head.unit_kind.try_map_ref(|k| visit_unit_kind(k, ctx));
ctx.symtab.close_scope();
let where_clauses = unit_where_clauses?
.iter()
.chain(scope_where_clauses.iter())
.cloned()
.collect();
Ok(hir::UnitHead {
name: head.name.clone(),
is_nonstatic_method: head.inputs.self_.is_some(),
inputs,
output_type,
unit_type_params,
scope_type_params,
unit_kind: unit_kind?,
where_clauses,
unsafe_marker: head.unsafe_token,
documentation: head.attributes.merge_docs(),
})
}
pub fn visit_const_generic(
t: &Loc<ast::Expression>,
ctx: &mut Context,
) -> Result<Loc<ConstGeneric>> {
let kind = match &t.inner {
ast::Expression::Identifier(name) => {
let (name, sym) = ctx.symtab.lookup_type_symbol(name)?;
match &sym.inner {
TypeSymbol::Declared(_, _) => {
return Err(Diagnostic::error(t, format!(
"{name} is not a type level integer but is used in a const generic expression."
),
)
.primary_label(format!("Expected type level integer"))
.secondary_label(&sym, format!("{name} is defined here")))
}
TypeSymbol::GenericArg { traits: _ }=> {
return Err(Diagnostic::error(
t,
format!(
"{name} is not a type level integer but is used in a const generic expression."
))
.primary_label("Expected type level integer")
.secondary_label(&sym, format!("{name} is defined here"))
.span_suggest_insert_before(
"Try making the generic an integer",
&sym,
"#int ",
)
.span_suggest_insert_before(
"or an unsigned integer",
&sym,
"#uint ",
))
}
TypeSymbol::GenericMeta(_) => {
ConstGeneric::Name(name.at_loc(t))
},
TypeSymbol::Alias(a) => {
return Err(Diagnostic::error(t, "Type aliases are not supported in const generics").primary_label("Type alias in const generic")
.secondary_label(a, "Alias defined here"))
}
}
}
ast::Expression::IntLiteral(val) => ConstGeneric::Int(val.inner.clone().as_signed()),
ast::Expression::StrLiteral(val) => ConstGeneric::Str(val.inner.clone()),
ast::Expression::BinaryOperator(lhs, op, rhs) => {
let lhs = visit_const_generic(lhs, ctx)?;
let rhs = visit_const_generic(rhs, ctx)?;
match &op.inner {
ast::BinaryOperator::Add => ConstGeneric::Add(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::Sub => ConstGeneric::Sub(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::Mul => ConstGeneric::Mul(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::Equals => ConstGeneric::Eq(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::NotEquals => ConstGeneric::NotEq(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::Div => ConstGeneric::Div(Box::new(lhs), Box::new(rhs)),
ast::BinaryOperator::Mod => ConstGeneric::Mod(Box::new(lhs), Box::new(rhs)),
other => {
return Err(Diagnostic::error(
op,
format!("Operator `{other}` is not supported in a type expression"),
)
.primary_label("Not supported in a type expression"))
}
}
}
ast::Expression::UnaryOperator(op, operand) => {
let operand = visit_const_generic(operand, ctx)?;
match &op.inner {
ast::UnaryOperator::Sub => ConstGeneric::Sub(
Box::new(ConstGeneric::Int(BigInt::zero()).at_loc(&operand)),
Box::new(operand),
),
other => {
return Err(Diagnostic::error(
t,
format!("Operator `{other}` is not supported in a type expression"),
)
.primary_label("Not supported in a type expression"))
}
}
}
ast::Expression::Call {
kind: CallKind::Function,
callee,
args,
turbofish: None,
} => match callee.as_strs().as_slice() {
["uint_bits_to_fit"] => match &args.inner {
ast::ArgumentList::Positional(a) => {
if a.len() != 1 {
return Err(Diagnostic::error(
args,
format!("This function takes one argument, {} provided", a.len()),
)
.primary_label("Expected 1 argument"));
} else {
let arg = visit_const_generic(&a[0], ctx)?;
ConstGeneric::UintBitsToFit(Box::new(arg))
}
}
ast::ArgumentList::Named(_) => {
return Err(Diagnostic::error(
t,
"Passing arguments by name is unsupported in type expressions",
)
.primary_label("Arguments passed by name in type expression"))
}
},
_ => {
return Err(Diagnostic::error(
callee,
format!("{callee} cannot be evaluated in a type expression"),
)
.primary_label("Not supported in a type expression"))
}
},
ast::Expression::Parenthesized(inner) => visit_const_generic(inner, ctx)?.inner,
_ => {
return Err(Diagnostic::error(
t,
format!("This expression is not supported in a type expression"),
)
.primary_label("Not supported in a type expression"))
}
};
Ok(kind.at_loc(t))
}
pub fn visit_where_clauses(
where_clauses: &[WhereClause],
ctx: &mut Context,
) -> Result<Vec<Loc<hir::WhereClause>>> {
let mut visited_where_clauses: Vec<Loc<hir::WhereClause>> = vec![];
for where_clause in where_clauses {
match where_clause {
WhereClause::GenericInt { target, expression } => {
let make_diag = |primary| {
Diagnostic::error(
target,
format!("Expected `{}` to be a type level integer", target),
)
.primary_label(primary)
.secondary_label(expression, "This is an integer constraint")
};
let (name_id, sym) = match ctx.symtab.lookup_type_symbol(target) {
Ok(res) => res,
Err(LookupError::NotATypeSymbol(_, thing)) => {
return Err(make_diag(format!("`{target}` is not a type level integer"))
.secondary_label(
thing.loc(),
format!("`{}` is a {} declared here", target, thing.kind_string()),
))
}
Err(e) => return Err(e.into()),
};
match &sym.inner {
TypeSymbol::GenericMeta(_) => {
let clause = hir::WhereClause::Int {
target: name_id.at_loc(target),
constraint: visit_const_generic(expression, ctx)?,
}
.between_locs(target, expression);
visited_where_clauses.push(clause);
}
TypeSymbol::GenericArg { .. } => {
return Err(
make_diag("Generic type in generic integer constraint".into())
.secondary_label(
sym.clone(),
format!("`{target}` is a generic type"),
)
.span_suggest_insert_before(
"Try making the generic an integer",
&sym,
"#int ",
)
.span_suggest_insert_before(
"or an unsigned integer",
&sym,
"#uint ",
),
);
}
TypeSymbol::Declared { .. } => {
return Err(make_diag(
"Declared type in generic integer constraint".into(),
)
.secondary_label(sym, format!("`{target}` is a type declared here")));
}
TypeSymbol::Alias(a) => {
return Err(Diagnostic::error(
&sym, "Type aliases are not supported in where clauses"
)
.primary_label("Type alias in where clause")
.secondary_label(a, "Alias defined here")
.note(
"This is a soft limitation in the compiler. If you need this feature, open an issue at https://gitlab.com/spade-lang/spade/"
)
)
}
}
}
WhereClause::TraitBounds { target, traits } => {
let make_diag = |primary| {
Diagnostic::error(
target,
format!("Expected `{}` to be a generic type", target),
)
.primary_label(primary)
};
let (name_id, sym) = match ctx.symtab.lookup_type_symbol(where_clause.target()) {
Ok(res) => res,
Err(LookupError::NotATypeSymbol(path, thing)) => {
return Err(make_diag(format!("`{target}` is not a type symbol"))
.secondary_label(
path,
format!("`{}` is {} declared here", target, thing.kind_string()),
));
}
Err(e) => return Err(e.into()),
};
match &sym.inner {
TypeSymbol::GenericArg { .. } | TypeSymbol::GenericMeta(MetaType::Type) => {
let traits = traits
.iter()
.map(|t| visit_trait_spec(t, &TypeSpecKind::TraitBound, ctx))
.collect::<Result<Vec<_>>>()?;
ctx.symtab.add_traits_to_generic(&name_id, traits.clone())?;
visited_where_clauses.push(
hir::WhereClause::Type {
target: name_id.at_loc(target),
traits,
}
.at_loc(target),
);
}
TypeSymbol::GenericMeta(_) => {
return Err(make_diag("Generic int in trait bound".into())
.secondary_label(sym, format!("{target} is a generic int")));
}
TypeSymbol::Declared { .. } => {
return Err(make_diag("Declared type in trait bound".into())
.secondary_label(sym, format!("{target} is a type declared here")));
}
TypeSymbol::Alias(a) => {
return Err(Diagnostic::error(
&sym, "Type aliases are not supported in where clauses"
)
.primary_label("Type alias in where clause")
.secondary_label(a, "Alias defined here")
.note(
"This is a soft limitation in the compiler. If you need this feature, open an issue at https://gitlab.com/spade-lang/spade/"
)
)
}
};
}
}
}
Ok(visited_where_clauses)
}
#[tracing::instrument(skip_all, fields(%unit.head.name, %unit.head.unit_kind))]
pub fn visit_unit(
extra_path: Option<Path>,
unit: &Loc<ast::Unit>,
scope_type_params: &Option<Loc<Vec<Loc<ast::TypeParam>>>>,
ctx: &mut Context,
) -> Result<hir::Item> {
let ast::Unit {
head:
ast::UnitHead {
unsafe_token: _,
extern_token: _,
name,
attributes,
inputs: _,
output_type: _,
unit_kind: _,
type_params,
where_clauses: _,
},
body,
} = &unit.inner;
ctx.symtab.new_scope();
let path = extra_path
.unwrap_or(Path(vec![]))
.join(Path(vec![name.clone()]))
.at_loc(&name.loc());
let (id, head) = ctx
.symtab
.lookup_unit(&path)
.map_err(|_| {
ctx.symtab.print_symbols();
println!("Failed to find {path} in symtab")
})
.expect("Attempting to lower an entity that has not been added to the symtab previously");
ctx.current_unit = Some(head.inner.clone());
let mut unit_name = if type_params.is_some() || scope_type_params.is_some() {
hir::UnitName::WithID(id.clone().at_loc(name))
} else {
hir::UnitName::FullPath(id.clone().at_loc(name))
};
let mut wal_suffix = None;
let attributes = attributes.lower(&mut |attr: &Loc<ast::Attribute>| match &attr.inner {
ast::Attribute::Optimize { passes } => Ok(Some(hir::Attribute::Optimize {
passes: passes.clone(),
})),
ast::Attribute::NoMangle { .. } => {
if let Some(generic_list) = type_params {
if body.is_some() {
Err(
Diagnostic::error(attr, "no_mangle is not allowed on generic units")
.primary_label("no_mangle not allowed here")
.secondary_label(generic_list, "Because this unit is generic"),
)
} else {
unit_name = hir::UnitName::Unmangled(name.0.clone(), id.clone().at_loc(name));
Ok(None)
}
} else if let Some(generic_list) = scope_type_params {
Err(Diagnostic::error(
attr,
"no_mangle is not allowed on units inside generic impls",
)
.primary_label("no_mangle not allowed here")
.secondary_label(generic_list, "Because this impl is generic"))
} else {
unit_name = hir::UnitName::Unmangled(name.0.clone(), id.clone().at_loc(name));
Ok(None)
}
}
ast::Attribute::WalSuffix { suffix } => {
if body.is_none() {
return Err(
Diagnostic::error(attr, "wal_suffix is not allowed on `extern` units")
.primary_label("Not allowed on `extern` units")
.secondary_label(unit.head.extern_token.unwrap(), "This unit is `extern`"),
);
}
wal_suffix = Some(suffix.clone());
Ok(None)
}
ast::Attribute::Documentation { .. } => Ok(None),
_ => Err(attr.report_unused("a unit")),
})?;
if body.is_none() {
return Ok(hir::Item::ExternUnit(unit_name, head));
}
let inputs = head
.inputs
.0
.iter()
.map(
|hir::Parameter {
name: ident,
ty,
no_mangle: _,
field_translator: _,
}| {
(
ctx.symtab.add_local_variable(ident.clone()).at_loc(ident),
ty.clone(),
)
},
)
.collect::<Vec<_>>();
for param in head.get_type_params() {
let hir::TypeParam {
ident,
name_id,
trait_bounds: _,
meta: _,
} = param.inner;
ctx.symtab.re_add_type(ident, name_id)
}
ctx.pipeline_ctx = maybe_perform_pipelining_tasks(unit, &head, ctx)?;
let mut body = body
.as_ref()
.unwrap()
.map_ref(|body| visit_expression(&body, ctx));
if let Some(suffix) = wal_suffix {
match &mut body.kind {
hir::ExprKind::Block(block) => {
block.statements.append(
&mut inputs
.iter()
.map(|(name, _)| {
hir::Statement::WalSuffixed {
suffix: suffix.inner.clone(),
target: name.clone(),
}
.at_loc(&suffix)
})
.collect(),
);
}
_ => diag_bail!(body, "Unit body was not block"),
}
}
ctx.symtab.close_scope();
ctx.current_unit = None;
Ok(hir::Item::Unit(expand_type_level_if(
hir::Unit {
name: unit_name,
head: head.clone().inner,
attributes,
inputs,
body,
}
.at_loc(unit),
ctx,
)?))
}
#[tracing::instrument(skip_all, fields(name=?trait_spec.path))]
pub fn visit_trait_spec(
trait_spec: &Loc<ast::TraitSpec>,
type_spec_kind: &TypeSpecKind,
ctx: &mut Context,
) -> Result<Loc<hir::TraitSpec>> {
let (name_id, loc) = match ctx.symtab.lookup_trait(&trait_spec.inner.path) {
Ok(res) => res,
Err(LookupError::IsAType(path)) => {
let (_, loc) = ctx.symtab.lookup_type_symbol(&path)?;
return Err(Diagnostic::error(
path.clone(),
format!("Unexpected type {}, expected a trait", path.inner),
)
.primary_label("Unexpected type")
.secondary_label(loc, format!("Type {} defined here", path.inner)));
}
Err(err) => return Err(err.into()),
};
let name = TraitName::Named(name_id.at_loc(&loc));
let type_params = match &trait_spec.inner.type_params {
Some(params) => Some(params.try_map_ref(|params| {
params
.iter()
.map(|param| param.try_map_ref(|te| visit_type_expression(te, type_spec_kind, ctx)))
.collect::<Result<_>>()
})?),
None => None,
};
Ok(hir::TraitSpec { name, type_params }.at_loc(trait_spec))
}
#[tracing::instrument(skip_all, fields(name=?item.name()))]
pub fn visit_item(item: &ast::Item, ctx: &mut Context) -> Result<Vec<hir::Item>> {
match item {
ast::Item::Unit(u) => Ok(vec![visit_unit(None, u, &None, ctx)?]),
ast::Item::TraitDef(_) => {
event!(Level::INFO, "Trait definition");
Ok(vec![])
}
ast::Item::Type(_) => {
event!(Level::INFO, "Type definition");
Ok(vec![])
}
ast::Item::ImplBlock(block) => visit_impl(block, ctx),
ast::Item::ExternalMod(_) => Ok(vec![]),
ast::Item::Module(m) => {
ctx.symtab.push_namespace(m.name.clone());
let result = visit_module(m, ctx);
ctx.symtab.pop_namespace();
result.map(|_| vec![])
}
ast::Item::Use(s) => match ctx.symtab.lookup_id(&s.path, &[]) {
Ok(_) => Ok(vec![]),
Err(lookup_error) => Err(lookup_error.into()),
},
}
}
#[tracing::instrument(skip_all, fields(module.name = %module.name.inner))]
pub fn visit_module(module: &ast::Module, ctx: &mut Context) -> Result<()> {
let path = &ctx
.symtab
.current_namespace()
.clone()
.at_loc(&module.name.loc());
let id = ctx
.symtab
.lookup_id(path, &[])
.map_err(|_| {
ctx.symtab.print_symbols();
println!("Failed to find {path} in symtab")
})
.expect("Attempting to lower a module that has not been added to the symtab previously");
let documentation = module.body.documentation.join("\n");
ctx.item_list.modules.insert(
id.clone(),
Module {
name: id.at_loc(&module.name),
documentation,
},
);
visit_module_body(&module.body, ctx)
}
#[tracing::instrument(skip_all)]
pub fn visit_module_body(body: &ast::ModuleBody, ctx: &mut Context) -> Result<()> {
for i in &body.members {
for item in visit_item(i, ctx)? {
match item {
hir::Item::Unit(u) => ctx
.item_list
.add_executable(u.name.name_id().clone(), ExecutableItem::Unit(u))?,
hir::Item::ExternUnit(name, head) => ctx.item_list.add_executable(
name.name_id().clone(),
ExecutableItem::ExternUnit(name, head),
)?,
}
}
}
Ok(())
}
fn try_lookup_enum_variant(path: &Loc<Path>, ctx: &mut Context) -> Result<hir::PatternKind> {
let (name_id, variant) = ctx.symtab.lookup_enum_variant(path)?;
if variant.inner.params.argument_num() == 0 {
Ok(hir::PatternKind::Type(name_id.at_loc(path), vec![]))
} else {
let expected = variant.inner.params.argument_num();
Err(Diagnostic::from(error::PatternListLengthMismatch {
expected,
got: 0,
at: path.loc(),
for_what: Some("enum variant".to_string()),
})
.span_suggest_insert_after(
"help: Add arguments here",
path.loc(),
format!(
"({})",
std::iter::repeat("_")
.take(expected)
.collect::<Vec<_>>()
.join(", ")
),
))
}
}
pub fn visit_pattern(p: &ast::Pattern, ctx: &mut Context) -> Result<hir::Pattern> {
let kind = match &p {
ast::Pattern::Integer(val) => hir::PatternKind::Integer(val.clone().as_signed()),
ast::Pattern::Bool(val) => hir::PatternKind::Bool(*val),
ast::Pattern::Path(path) => {
match (try_lookup_enum_variant(path, ctx), path.inner.0.as_slice()) {
(Ok(kind), _) => kind,
(_, [ident]) => {
let (name_id, pre_declared) =
if let Some(state) = ctx.symtab.get_declaration(ident) {
match state.inner {
DeclarationState::Undefined(id) => {
ctx.symtab
.mark_declaration_defined(ident.clone(), ident.loc());
(id, true)
}
DeclarationState::Undecleared(id) => {
ctx.symtab.add_thing_with_name_id(
id.clone(),
Thing::Variable(ident.clone()),
);
ctx.symtab
.mark_declaration_defined(ident.clone(), ident.loc());
(id, true)
}
DeclarationState::Defined(previous) => {
return Err(Diagnostic::error(
ident,
format!("{ident} was already defined"),
)
.secondary_label(previous, "First defined here")
.primary_label("Later defined here")
.secondary_label(state.loc(), format!("{ident} declared here"))
.note("Declared variables can only be defined once"));
}
}
} else {
(
ctx.symtab.add_thing(
Path::ident(ident.clone()),
Thing::Variable(ident.clone()),
),
false,
)
};
hir::PatternKind::Name {
name: name_id.at_loc(ident),
pre_declared,
}
}
(_, []) => unreachable!(),
(Err(e), _) => return Err(e),
}
}
ast::Pattern::Tuple(pattern) => {
let inner = pattern
.iter()
.map(|p| p.try_map_ref(|p| visit_pattern(p, ctx)))
.collect::<Result<_>>()?;
hir::PatternKind::Tuple(inner)
}
ast::Pattern::Array(patterns) => {
let inner = patterns
.iter()
.map(|p| p.try_map_ref(|p| visit_pattern(p, ctx)))
.collect::<Result<_>>()?;
hir::PatternKind::Array(inner)
}
ast::Pattern::Type(path, args) => {
let (name_id, p) = ctx.symtab.lookup_patternable_type(path)?;
match &args.inner {
ast::ArgumentPattern::Named(patterns) => {
let mut bound = HashSet::<Loc<Identifier>>::new();
let mut unbound = p
.params
.0
.iter()
.map(
|hir::Parameter {
name: ident,
ty: _,
no_mangle: _,
field_translator: _,
}| ident.inner.clone(),
)
.collect::<HashSet<_>>();
let mut patterns = patterns
.iter()
.map(|(target, pattern)| {
let ast_pattern = pattern.as_ref().cloned().unwrap_or_else(|| {
ast::Pattern::Path(Path(vec![target.clone()]).at_loc(target))
.at_loc(target)
});
let new_pattern = visit_pattern(&ast_pattern, ctx)?;
if let Some(prev) = bound.get(target) {
return Err(Diagnostic::from(
ArgumentError::DuplicateNamedBindings {
new: target.clone(),
prev_loc: prev.loc(),
},
));
}
bound.insert(target.clone());
if unbound.take(target).is_none() {
return Err(Diagnostic::from(ArgumentError::NoSuchArgument {
name: target.clone(),
}));
}
let kind = match pattern {
Some(_) => hir::ArgumentKind::Named,
None => hir::ArgumentKind::ShortNamed,
};
Ok(hir::PatternArgument {
target: target.clone(),
value: new_pattern.at_loc(&ast_pattern),
kind,
})
})
.collect::<Result<Vec<_>>>()?;
if !unbound.is_empty() {
return Err(Diagnostic::from(ArgumentError::MissingArguments {
missing: unbound.into_iter().collect(),
at: args.loc(),
}));
}
patterns.sort_by_cached_key(|arg| p.params.arg_index(&arg.target).unwrap());
hir::PatternKind::Type(name_id.at_loc(path), patterns)
}
ast::ArgumentPattern::Positional(patterns) => {
if p.params.argument_num() != patterns.len() {
return Err(Diagnostic::from(error::PatternListLengthMismatch {
expected: p.params.argument_num(),
got: patterns.len(),
at: args.loc(),
for_what: None,
}));
}
let patterns = patterns
.iter()
.zip(p.params.0.iter())
.map(|(p, arg)| {
let pat = p.try_map_ref(|p| visit_pattern(p, ctx))?;
Ok(hir::PatternArgument {
target: arg.name.clone(),
value: pat,
kind: hir::ArgumentKind::Positional,
})
})
.collect::<Result<Vec<_>>>()?;
hir::PatternKind::Type(name_id.at_loc(path), patterns)
}
}
}
};
Ok(kind.with_id(ctx.idtracker.next()))
}
fn try_visit_statement(
s: &Loc<ast::Statement>,
ctx: &mut Context,
) -> Result<Vec<Loc<hir::Statement>>> {
match &s.inner {
ast::Statement::Declaration(names) => {
let names = names
.iter()
.map(|name| {
ctx.symtab
.add_declaration(name.clone())
.map(|decl| decl.at_loc(name))
})
.filter_map(|name| name.handle_in(&mut ctx.diags))
.collect::<Vec<_>>();
Ok(vec![hir::Statement::Declaration(names).at_loc(s)])
}
ast::Statement::Binding(Binding {
pattern,
ty,
value,
attrs,
}) => {
let mut stmts = vec![];
let hir_type = if let Some(t) = ty {
Some(visit_type_spec(t, &TypeSpecKind::BindingType, ctx)?)
} else {
None
};
let value = value.visit(visit_expression, ctx);
let pattern = pattern.try_visit(visit_pattern, ctx)?;
let mut wal_trace = None;
attrs.lower(&mut |attr| match &attr.inner {
ast::Attribute::WalTrace { clk, rst } => {
wal_trace = Some(
WalTrace {
clk: clk.as_ref().map(|x| x.visit(visit_expression, ctx)),
rst: rst.as_ref().map(|x| x.visit(visit_expression, ctx)),
}
.at_loc(attr),
);
Ok(None)
}
ast::Attribute::WalSuffix { suffix } => {
for name in pattern.get_names() {
stmts.push(
hir::Statement::WalSuffixed {
suffix: suffix.inner.clone(),
target: name.clone(),
}
.at_loc(suffix),
);
}
Ok(None)
}
ast::Attribute::NoMangle { .. }
| ast::Attribute::Fsm { .. }
| ast::Attribute::Optimize { .. }
| ast::Attribute::Documentation { .. }
| ast::Attribute::SurferTranslator(_)
| ast::Attribute::WalTraceable { .. } => Err(attr.report_unused("let binding")),
})?;
stmts.push(
hir::Statement::Binding(hir::Binding {
pattern,
ty: hir_type,
value,
wal_trace,
})
.at_loc(s),
);
Ok(stmts)
}
ast::Statement::Expression(expr) => {
let value = expr.visit(visit_expression, ctx);
Ok(vec![hir::Statement::Expression(value).at_loc(expr)])
}
ast::Statement::Register(inner) => visit_register(inner, ctx),
ast::Statement::PipelineRegMarker(count, cond) => {
let cond = match cond {
Some(cond) => Some(cond.visit(visit_expression, ctx)),
None => None,
};
let extra = match (count, cond) {
(None, None) => None,
(Some(count), None) => Some(hir::PipelineRegMarkerExtra::Count {
count: count.try_map_ref(|c| {
visit_type_expression(c, &TypeSpecKind::PipelineRegCount, ctx)
})?,
count_typeexpr_id: ctx.idtracker.next(),
}),
(None, Some(cond)) => Some(hir::PipelineRegMarkerExtra::Condition(cond)),
(Some(count), Some(cond)) => {
return Err(Diagnostic::error(
count,
"Multiple registers with conditions can not be defined",
)
.primary_label("Multiple registers not allowed")
.secondary_label(cond, "Condition specified here")
.help("Consider splitting into two reg statements"));
}
};
Ok(vec![hir::Statement::PipelineRegMarker(extra).at_loc(s)])
}
ast::Statement::Label(name) => {
let (name, sym) = ctx
.symtab
.lookup_type_symbol(&Path::ident(name.clone()).at_loc(name))?;
Ok(vec![hir::Statement::Label(name.at_loc(&sym)).at_loc(s)])
}
ast::Statement::Assert(expr) => {
let expr = expr.visit(visit_expression, ctx);
Ok(vec![hir::Statement::Assert(expr).at_loc(s)])
}
ast::Statement::Set { target, value } => {
let target = target.visit(visit_expression, ctx);
let value = value.visit(visit_expression, ctx);
Ok(vec![hir::Statement::Set { target, value }.at_loc(s)])
}
}
}
fn visit_statement(s: &Loc<ast::Statement>, ctx: &mut Context) -> Vec<Loc<hir::Statement>> {
match try_visit_statement(s, ctx) {
Ok(result) => result,
Err(e) => {
ctx.diags.errors.push(e);
vec![hir::Statement::Error.at_loc(s)]
}
}
}
#[tracing::instrument(skip_all)]
fn visit_argument_list(
arguments: &ast::ArgumentList,
ctx: &mut Context,
) -> Result<hir::ArgumentList<hir::Expression>> {
match arguments {
ast::ArgumentList::Positional(args) => {
let inner = args
.iter()
.map(|arg| arg.visit(visit_expression, ctx))
.collect::<_>();
Ok(hir::ArgumentList::Positional(inner))
}
ast::ArgumentList::Named(args) => {
let inner = args
.iter()
.map(|arg| match arg {
ast::NamedArgument::Full(name, expr) => {
Ok(hir::expression::NamedArgument::Full(
name.clone(),
expr.visit(visit_expression, ctx),
))
}
ast::NamedArgument::Short(name) => {
let expr =
ast::Expression::Identifier(Path(vec![name.clone()]).at_loc(name))
.at_loc(name);
Ok(hir::expression::NamedArgument::Short(
name.clone(),
expr.visit(visit_expression, ctx),
))
}
})
.collect::<Result<_>>()?;
Ok(hir::ArgumentList::Named(inner))
}
}
}
pub fn visit_call_kind(
kind: &ast::CallKind,
ctx: &mut Context,
) -> Result<hir::expression::CallKind> {
Ok(match kind {
ast::CallKind::Function => hir::expression::CallKind::Function,
ast::CallKind::Entity(loc) => hir::expression::CallKind::Entity(*loc),
ast::CallKind::Pipeline(loc, depth) => {
let depth = depth
.try_map_ref(|e| visit_type_expression(e, &TypeSpecKind::PipelineInstDepth, ctx))?;
hir::expression::CallKind::Pipeline {
inst_loc: *loc,
depth,
depth_typeexpr_id: ctx.idtracker.next(),
}
}
})
}
pub fn visit_turbofish(
t: &Loc<ast::TurbofishInner>,
ctx: &mut Context,
) -> Result<Loc<hir::ArgumentList<TypeExpression>>> {
t.try_map_ref(|args| match args {
ast::TurbofishInner::Named(fishes) => fishes
.iter()
.map(|fish| match &fish.inner {
ast::NamedTurbofish::Short(name) => {
let arg = ast::TypeExpression::TypeSpec(Box::new(
ast::TypeSpec::Named(Path(vec![name.clone()]).at_loc(name), None)
.at_loc(name),
));
let arg =
visit_type_expression(&arg, &TypeSpecKind::Turbofish, ctx)?.at_loc(name);
Ok(hir::expression::NamedArgument::Short(name.clone(), arg))
}
ast::NamedTurbofish::Full(name, arg) => {
let arg =
visit_type_expression(arg, &TypeSpecKind::Turbofish, ctx)?.at_loc(arg);
Ok(hir::expression::NamedArgument::Full(name.clone(), arg))
}
})
.collect::<Result<Vec<_>>>()
.map(|params| hir::ArgumentList::Named(params)),
ast::TurbofishInner::Positional(args) => args
.iter()
.map(|arg| {
arg.try_map_ref(|arg| visit_type_expression(arg, &TypeSpecKind::Turbofish, ctx))
})
.collect::<Result<_>>()
.map(hir::ArgumentList::Positional),
})
}
fn visit_expression_result(e: &ast::Expression, ctx: &mut Context) -> Result<hir::ExprKind> {
match e {
ast::Expression::IntLiteral(val) => {
let kind = match &val.inner {
ast::IntLiteral::Unsized(_) => IntLiteralKind::Unsized,
ast::IntLiteral::Signed { val: _, size } => IntLiteralKind::Signed(size.clone()),
ast::IntLiteral::Unsigned { val: _, size } => {
IntLiteralKind::Unsigned(size.clone())
}
};
Ok(hir::ExprKind::IntLiteral(
val.inner.clone().as_signed(),
kind,
))
}
ast::Expression::BoolLiteral(val) => Ok(hir::ExprKind::BoolLiteral(val.inner)),
ast::Expression::StrLiteral(val) => Err(Diagnostic::error(
val,
"Strings are not supported inside expressions",
)),
ast::Expression::TriLiteral(lit) => {
let result = match lit.inner {
ast::BitLiteral::Low => hir::expression::TriLiteral::Low,
ast::BitLiteral::High => hir::expression::TriLiteral::High,
ast::BitLiteral::HighImp => hir::expression::TriLiteral::HighImp,
};
Ok(hir::ExprKind::TriLiteral(result))
}
ast::Expression::CreatePorts => Ok(hir::ExprKind::CreatePorts),
ast::Expression::BinaryOperator(lhs, tok, rhs) => {
let lhs = lhs.visit(visit_expression, ctx);
let rhs = rhs.visit(visit_expression, ctx);
let operator = |op: BinaryOperator| {
hir::ExprKind::BinaryOperator(Box::new(lhs), op.at_loc(tok), Box::new(rhs))
};
match tok.inner {
ast::BinaryOperator::Add => Ok(operator(BinaryOperator::Add)),
ast::BinaryOperator::Sub => Ok(operator(BinaryOperator::Sub)),
ast::BinaryOperator::Mul => Ok(operator(BinaryOperator::Mul)),
ast::BinaryOperator::Div => Ok(operator(BinaryOperator::Div)),
ast::BinaryOperator::Mod => Ok(operator(BinaryOperator::Mod)),
ast::BinaryOperator::Equals => Ok(operator(BinaryOperator::Eq)),
ast::BinaryOperator::NotEquals => Ok(operator(BinaryOperator::NotEq)),
ast::BinaryOperator::Gt => Ok(operator(BinaryOperator::Gt)),
ast::BinaryOperator::Lt => Ok(operator(BinaryOperator::Lt)),
ast::BinaryOperator::Ge => Ok(operator(BinaryOperator::Ge)),
ast::BinaryOperator::Le => Ok(operator(BinaryOperator::Le)),
ast::BinaryOperator::LeftShift => Ok(operator(BinaryOperator::LeftShift)),
ast::BinaryOperator::RightShift => Ok(operator(BinaryOperator::RightShift)),
ast::BinaryOperator::ArithmeticRightShift => {
Ok(operator(BinaryOperator::ArithmeticRightShift))
}
ast::BinaryOperator::LogicalAnd => Ok(operator(BinaryOperator::LogicalAnd)),
ast::BinaryOperator::LogicalOr => Ok(operator(BinaryOperator::LogicalOr)),
ast::BinaryOperator::LogicalXor => Ok(operator(BinaryOperator::LogicalXor)),
ast::BinaryOperator::BitwiseOr => Ok(operator(BinaryOperator::BitwiseOr)),
ast::BinaryOperator::BitwiseAnd => Ok(operator(BinaryOperator::BitwiseAnd)),
ast::BinaryOperator::BitwiseXor => Ok(operator(BinaryOperator::BitwiseXor)),
}
}
ast::Expression::UnaryOperator(operator, operand) => {
let operand = operand.visit(visit_expression, ctx);
let unop = |op: hir::expression::UnaryOperator| {
hir::ExprKind::UnaryOperator(op.at_loc(operator), Box::new(operand))
};
match operator.inner {
ast::UnaryOperator::Sub => Ok(unop(hir::expression::UnaryOperator::Sub)),
ast::UnaryOperator::Not => Ok(unop(hir::expression::UnaryOperator::Not)),
ast::UnaryOperator::BitwiseNot => {
Ok(unop(hir::expression::UnaryOperator::BitwiseNot))
}
ast::UnaryOperator::Dereference => {
Ok(unop(hir::expression::UnaryOperator::Dereference))
}
ast::UnaryOperator::Reference => {
Ok(unop(hir::expression::UnaryOperator::Reference))
}
}
}
ast::Expression::Parenthesized(expr) => visit_expression_result(expr, ctx),
ast::Expression::TupleLiteral(exprs) => {
let exprs = exprs
.iter()
.map(|e| e.visit(visit_expression, ctx))
.collect::<Vec<_>>();
Ok(hir::ExprKind::TupleLiteral(exprs))
}
ast::Expression::ArrayLiteral(exprs) => {
let exprs = exprs
.iter()
.map(|e| e.visit(visit_expression, ctx))
.collect::<Vec<_>>();
Ok(hir::ExprKind::ArrayLiteral(exprs))
}
ast::Expression::ArrayShorthandLiteral(expr, amount) => {
Ok(hir::ExprKind::ArrayShorthandLiteral(
Box::new(visit_expression(expr, ctx).at_loc(expr)),
visit_const_generic(amount, ctx)?.map(|c| c.with_id(ctx.idtracker.next())),
))
}
ast::Expression::Index(target, index) => {
let target = target.visit(visit_expression, ctx);
let index = index.visit(visit_expression, ctx);
Ok(hir::ExprKind::Index(Box::new(target), Box::new(index)))
}
ast::Expression::RangeIndex { target, start, end } => {
let target = target.visit(visit_expression, ctx);
Ok(hir::ExprKind::RangeIndex {
target: Box::new(target),
start: visit_const_generic(start, ctx)?.map(|c| c.with_id(ctx.idtracker.next())),
end: visit_const_generic(end, ctx)?.map(|c| c.with_id(ctx.idtracker.next())),
})
}
ast::Expression::TupleIndex(tuple, index) => Ok(hir::ExprKind::TupleIndex(
Box::new(tuple.visit(visit_expression, ctx)),
*index,
)),
ast::Expression::FieldAccess(target, field) => Ok(hir::ExprKind::FieldAccess(
Box::new(target.visit(visit_expression, ctx)),
field.clone(),
)),
ast::Expression::MethodCall {
kind,
target,
name,
args,
turbofish,
} => {
let target = target.visit(visit_expression, ctx);
Ok(hir::ExprKind::MethodCall {
target: Box::new(target),
name: name.clone(),
args: args.try_map_ref(|args| visit_argument_list(args, ctx))?,
call_kind: visit_call_kind(kind, ctx)?,
turbofish: turbofish
.as_ref()
.map(|t| visit_turbofish(t, ctx))
.transpose()?,
safety: ctx.safety,
})
}
ast::Expression::If(cond, ontrue, onfalse) => {
let cond = cond.visit(visit_expression, ctx);
let ontrue = ontrue.visit(visit_expression, ctx);
let onfalse = onfalse.visit(visit_expression, ctx);
Ok(hir::ExprKind::If(
Box::new(cond),
Box::new(ontrue),
Box::new(onfalse),
))
}
ast::Expression::TypeLevelIf(cond, ontrue, onfalse) => {
let cond = visit_const_generic(cond, ctx)?;
let ontrue = ontrue.visit(visit_expression, ctx);
let onfalse = onfalse.visit(visit_expression, ctx);
Ok(hir::ExprKind::TypeLevelIf(
cond.map(|cond| cond.with_id(ctx.idtracker.next())),
Box::new(ontrue),
Box::new(onfalse),
))
}
ast::Expression::Match(expression, branches) => {
let e = expression.visit(visit_expression, ctx);
if branches.is_empty() {
return Err(Diagnostic::error(branches, "Match body has no arms")
.primary_label("This match body has no arms"));
}
let b = branches
.iter()
.map(|(pattern, result)| {
ctx.symtab.new_scope();
let p = pattern.try_visit(visit_pattern, ctx)?;
let r = result.visit(visit_expression, ctx);
ctx.symtab.close_scope();
Ok((p, r))
})
.collect::<Result<Vec<_>>>()?;
Ok(hir::ExprKind::Match(Box::new(e), b))
}
ast::Expression::Block(block) => {
Ok(hir::ExprKind::Block(Box::new(visit_block(block, ctx)?)))
}
ast::Expression::Unsafe(block) => {
let outside = ::core::mem::replace(&mut ctx.safety, Safety::Unsafe);
if outside == Safety::Unsafe {
ctx.diags.errors.push(
Diagnostic::warning(block.loc(), "Unnecessary unsafe block")
.note("This block is already in unsafe context"),
);
}
let res = visit_block(block, ctx);
ctx.safety = outside;
Ok(hir::ExprKind::Block(Box::new(res?)))
}
ast::Expression::Lambda { .. } => {
let outside = ::core::mem::replace(&mut ctx.safety, Safety::Default);
let res = visit_lambda(e, ctx);
ctx.safety = outside;
res
}
ast::Expression::Call {
kind,
callee,
args,
turbofish,
} => {
let (name_id, _) = ctx.symtab.lookup_unit(callee)?;
let args = visit_argument_list(args, ctx)?.at_loc(args);
let kind = visit_call_kind(kind, ctx)?;
let turbofish = turbofish
.as_ref()
.map(|t| visit_turbofish(t, ctx))
.transpose()?;
Ok(hir::ExprKind::Call {
kind,
callee: name_id.at_loc(callee),
args,
turbofish,
safety: ctx.safety,
})
}
ast::Expression::Identifier(path) => {
match ctx.symtab.lookup_variable(path) {
Ok(id) => Ok(hir::ExprKind::Identifier(id)),
Err(LookupError::IsAType(_)) => {
let ty = ctx.symtab.lookup_type_symbol(path)?;
let (name, ty) = &ty;
match ty.inner {
TypeSymbol::GenericMeta(
MetaType::Int | MetaType::Uint | MetaType::Number | MetaType::Str,
) => Ok(hir::ExprKind::TypeLevelInteger(name.clone())),
TypeSymbol::GenericMeta(_) | TypeSymbol::GenericArg { traits: _ } => {
Err(Diagnostic::error(
path,
format!("Generic type {name} is a type but it is used as a value"),
)
.primary_label(format!("{name} is a type"))
.secondary_label(ty, format!("{name} is declared here"))
.span_suggest_insert_before(
format!("Consider making `{name}` a type level integer"),
ty,
"#int ",
)
.span_suggest_insert_before(
format!("or a type level uint"),
ty,
"#uint ",
))
}
TypeSymbol::Declared(_, _) | TypeSymbol::Alias(_) => Err(
Diagnostic::error(path, format!("Type {name} is used as a value"))
.primary_label(format!("{name} is a type")),
),
}
}
Err(LookupError::NotAVariable(path, ref was @ Thing::EnumVariant(_)))
| Err(LookupError::NotAVariable(
path,
ref was @ Thing::Alias {
path: _,
in_namespace: _,
},
)) => {
let (name_id, variant) = match ctx.symtab.lookup_enum_variant(&path) {
Ok(res) => res,
Err(_) => return Err(LookupError::NotAValue(path, was.clone()).into()),
};
if variant.params.0.is_empty() {
let callee = name_id.at_loc(&path);
let args = hir::ArgumentList::Positional(vec![]).at_loc(&path);
Ok(hir::ExprKind::Call {
kind: hir::expression::CallKind::Function,
callee,
args,
turbofish: None,
safety: ctx.safety,
})
} else {
Err(LookupError::NotAValue(path, was.clone()).into())
}
}
Err(LookupError::NotAVariable(path, was)) => {
Err(LookupError::NotAValue(path, was).into())
}
Err(err) => Err(err.into()),
}
}
ast::Expression::PipelineReference {
stage_kw_and_reference_loc,
stage,
name,
} => {
let stage = match stage {
ast::PipelineStageReference::Relative(offset) => {
hir::expression::PipelineRefKind::Relative(offset.try_map_ref(|t| {
visit_type_expression(t, &TypeSpecKind::PipelineInstDepth, ctx)
})?)
}
ast::PipelineStageReference::Absolute(name) => {
hir::expression::PipelineRefKind::Absolute(
ctx.symtab
.lookup_type_symbol(&Path::ident(name.clone()).at_loc(name))?
.0
.at_loc(name),
)
}
}
.at_loc(stage_kw_and_reference_loc);
let pipeline_ctx = ctx
.pipeline_ctx
.as_ref()
.expect("Expected to have a pipeline context");
let path = Path(vec![name.clone()]).at_loc(name);
let (name_id, declares_name) = match ctx.symtab.try_lookup_variable(&path)? {
Some(id) => (id.at_loc(name), false),
None => {
let pipeline_offset = ctx.symtab.current_scope() - pipeline_ctx.scope;
let undecleared_lookup = ctx.symtab.declarations[pipeline_ctx.scope].get(name);
if let Some(DeclarationState::Undecleared(name_id)) = undecleared_lookup {
(name_id.clone().at_loc(name), false)
} else {
let name_id = ctx
.symtab
.add_undecleared_at_offset(pipeline_offset, name.clone());
(name_id.at_loc(name), true)
}
}
};
Ok(hir::ExprKind::PipelineRef {
stage,
name: name_id,
declares_name,
depth_typeexpr_id: ctx.idtracker.next(),
})
}
ast::Expression::StageReady => Ok(hir::ExprKind::StageReady),
ast::Expression::StageValid => Ok(hir::ExprKind::StageValid),
ast::Expression::StaticUnreachable(message) => {
Ok(hir::ExprKind::StaticUnreachable(message.clone()))
}
}
}
fn check_for_undefined(ctx: &mut Context) {
for problem in &ctx.symtab.get_undefined_declarations() {
match problem {
(undefined, DeclarationState::Undefined(_)) => {
Err(
Diagnostic::error(undefined, "Declared variable is not defined")
.primary_label("This variable is declared but not defined")
.help(format!("Define {undefined} with a let or reg binding"))
.help("Or, remove the declaration if the variable is unused"),
)
}
(undecleared, DeclarationState::Undecleared(_)) => Err(Diagnostic::error(
undecleared,
"Could not find referenced variable",
)
.primary_label("This variable could not be found")),
(_, _) => Ok(()),
}
.handle_in(&mut ctx.diags);
}
}
#[recursive]
#[tracing::instrument(skip_all, fields(kind=e.variant_str()))]
pub fn visit_expression(e: &ast::Expression, ctx: &mut Context) -> hir::Expression {
let new_id = ctx.idtracker.next();
let kind = match visit_expression_result(e, ctx) {
Ok(kind) => kind,
Err(e) => {
ctx.diags.errors.push(e);
ExprKind::Error
}
};
kind.with_id(new_id)
}
fn visit_block(b: &ast::Block, ctx: &mut Context) -> Result<hir::Block> {
ctx.symtab.new_scope();
let statements = b
.statements
.iter()
.map(|statement| visit_statement(statement, ctx))
.collect::<Vec<_>>()
.into_iter()
.flatten()
.collect::<Vec<_>>();
let result = b.result.as_ref().map(|o| o.visit(visit_expression, ctx));
check_for_undefined(ctx);
ctx.symtab.close_scope();
Ok(hir::Block { statements, result })
}
fn visit_register(reg: &Loc<ast::Register>, ctx: &mut Context) -> Result<Vec<Loc<hir::Statement>>> {
let (reg, loc) = reg.split_loc_ref();
let pattern = reg.pattern.try_visit(visit_pattern, ctx)?;
let clock = reg.clock.visit(visit_expression, ctx);
let reset = if let Some((trig, value)) = ®.reset {
Some((
trig.visit(visit_expression, ctx),
value.visit(visit_expression, ctx),
))
} else {
None
};
let initial = reg.initial.as_ref().map(|i| i.visit(visit_expression, ctx));
let value = reg.value.visit(visit_expression, ctx);
let value_type = if let Some(value_type) = ®.value_type {
Some(visit_type_spec(
value_type,
&TypeSpecKind::BindingType,
ctx,
)?)
} else {
None
};
let mut stmts = vec![];
let attributes = reg
.attributes
.lower(&mut |attr| match &attr.inner {
ast::Attribute::Fsm { state } => {
let name_id = if let Some(state) = state {
ctx.symtab
.lookup_variable(&Path(vec![state.clone()]).at_loc(state))?
} else if let PatternKind::Name { name, .. } = &pattern.inner.kind {
name.inner.clone()
} else {
return Err(Diagnostic::error(
attr,
"#[fsm] without explicit name on non-name pattern",
)
.secondary_label(&pattern, "This is a pattern")
.span_suggest(
"Consider specifying the name of the s ignal containing the state",
attr,
"#[fsm(<name>)]",
));
};
Ok(Some(hir::Attribute::Fsm { state: name_id }))
}
ast::Attribute::WalSuffix { suffix } => {
for name in pattern.get_names() {
stmts.push(
hir::Statement::WalSuffixed {
suffix: suffix.inner.clone(),
target: name.clone(),
}
.at_loc(suffix),
);
}
Ok(None)
}
_ => Err(attr.report_unused("a register")),
})
.handle_in(&mut ctx.diags)
.unwrap_or_else(|| hir::AttributeList::empty());
stmts.push(
hir::Statement::Register(hir::Register {
pattern,
clock,
reset,
initial,
value,
value_type,
attributes,
})
.at_loc(&loc),
);
Ok(stmts)
}
#[cfg(test)]
mod statement_visiting {
use super::*;
use crate::testutil::test_context;
use id_tracker::ExprID;
use pretty_assertions::assert_eq;
use spade_ast::testutil::{ast_ident, ast_path};
use spade_common::location_info::WithLocation;
use spade_common::name::testutil::name_id;
#[test]
fn bindings_convert_correctly() {
let mut ctx = test_context();
let input = ast::Statement::binding(
ast::Pattern::name("a"),
Some(ast::TypeSpec::Tuple(Vec::new()).nowhere()),
ast::Expression::int_literal_signed(0).nowhere(),
)
.nowhere();
let expected = hir::Statement::binding(
hir::PatternKind::name(name_id(0, "a")).idless().nowhere(),
Some(hir::TypeSpec::unit().nowhere()),
hir::ExprKind::int_literal(0).idless().nowhere(),
)
.nowhere();
assert_eq!(visit_statement(&input, &mut ctx), vec![expected]);
assert_eq!(ctx.symtab.has_symbol(ast_path("a").inner), true);
}
#[test]
fn registers_are_statements() {
let input = ast::Statement::Register(
ast::Register {
pattern: ast::Pattern::name("regname"),
clock: ast::Expression::Identifier(ast_path("clk")).nowhere(),
reset: None,
initial: None,
value: ast::Expression::int_literal_signed(0).nowhere(),
value_type: None,
attributes: ast::AttributeList::empty(),
}
.nowhere(),
)
.nowhere();
let expected = hir::Statement::Register(hir::Register {
pattern: hir::PatternKind::name(name_id(1, "regname"))
.idless()
.nowhere(),
clock: hir::ExprKind::Identifier(name_id(0, "clk").inner)
.with_id(ExprID(0))
.nowhere(),
reset: None,
initial: None,
value: hir::ExprKind::int_literal(0).idless().nowhere(),
value_type: None,
attributes: hir::AttributeList::empty(),
})
.nowhere();
let mut ctx = test_context();
let clk_id = ctx.symtab.add_local_variable(ast_ident("clk"));
assert_eq!(clk_id.0, 0);
assert_eq!(visit_statement(&input, &mut ctx), vec![expected]);
assert_eq!(ctx.symtab.has_symbol(ast_path("regname").inner), true);
}
#[test]
fn declarations_declare_variables() {
let input = ast::Statement::Declaration(vec![ast_ident("x"), ast_ident("y")]).nowhere();
let ctx = &mut test_context();
assert_eq!(
visit_statement(&input, ctx),
vec![hir::Statement::Declaration(vec![name_id(0, "x"), name_id(1, "y")]).nowhere()]
);
assert_eq!(ctx.symtab.has_symbol(ast_path("x").inner), true);
assert_eq!(ctx.symtab.has_symbol(ast_path("y").inner), true);
}
}
#[cfg(test)]
mod expression_visiting {
use super::*;
use crate::testutil::test_context;
use hir::hparams;
use hir::symbol_table::EnumVariant;
use spade_ast::testutil::{ast_ident, ast_path};
use spade_common::location_info::WithLocation;
use spade_common::name::testutil::name_id;
#[test]
fn int_literals_work() {
let input = ast::Expression::int_literal_signed(123);
let expected = hir::ExprKind::int_literal(123).idless();
assert_eq!(visit_expression(&input, &mut test_context()), expected);
}
macro_rules! binop_test {
($test_name:ident, $token:ident, $op:ident) => {
#[test]
fn $test_name() {
let input = ast::Expression::BinaryOperator(
Box::new(ast::Expression::int_literal_signed(123).nowhere()),
spade_ast::BinaryOperator::$token.nowhere(),
Box::new(ast::Expression::int_literal_signed(456).nowhere()),
);
let expected = hir::ExprKind::BinaryOperator(
Box::new(hir::ExprKind::int_literal(123).idless().nowhere()),
BinaryOperator::$op.nowhere(),
Box::new(hir::ExprKind::int_literal(456).idless().nowhere()),
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context()), expected);
}
};
}
macro_rules! unop_test {
($test_name:ident, $token:ident, $op:ident) => {
#[test]
fn $test_name() {
let input = ast::Expression::UnaryOperator(
spade_ast::UnaryOperator::$token.nowhere(),
Box::new(ast::Expression::int_literal_signed(456).nowhere()),
);
let expected = hir::ExprKind::UnaryOperator(
hir::expression::UnaryOperator::$op.nowhere(),
Box::new(hir::ExprKind::int_literal(456).idless().nowhere()),
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context()), expected);
}
};
}
binop_test!(additions, Add, Add);
binop_test!(subtractions, Sub, Sub);
binop_test!(equals, Equals, Eq);
binop_test!(bitwise_or, BitwiseOr, BitwiseOr);
binop_test!(bitwise_and, BitwiseAnd, BitwiseAnd);
unop_test!(usub, Sub, Sub);
unop_test!(not, Not, Not);
#[test]
fn indexing_works() {
let input = ast::Expression::Index(
Box::new(ast::Expression::int_literal_signed(0).nowhere()),
Box::new(ast::Expression::int_literal_signed(1).nowhere()),
);
let expected = hir::ExprKind::Index(
Box::new(hir::ExprKind::int_literal(0).idless().nowhere()),
Box::new(hir::ExprKind::int_literal(1).idless().nowhere()),
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context()), expected);
}
#[test]
fn field_access_works() {
let input = ast::Expression::FieldAccess(
Box::new(ast::Expression::int_literal_signed(0).nowhere()),
ast_ident("a"),
);
let expected = hir::ExprKind::FieldAccess(
Box::new(hir::ExprKind::int_literal(0).idless().nowhere()),
ast_ident("a"),
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context(),), expected);
}
#[test]
fn blocks_work() {
let input = ast::Expression::Block(Box::new(ast::Block {
statements: vec![ast::Statement::binding(
ast::Pattern::name("a"),
None,
ast::Expression::int_literal_signed(0).nowhere(),
)
.nowhere()],
result: Some(ast::Expression::int_literal_signed(0).nowhere()),
}));
let expected = hir::ExprKind::Block(Box::new(hir::Block {
statements: vec![hir::Statement::binding(
hir::PatternKind::name(name_id(0, "a")).idless().nowhere(),
None,
hir::ExprKind::int_literal(0).idless().nowhere(),
)
.nowhere()],
result: Some(hir::ExprKind::int_literal(0).idless().nowhere()),
}))
.idless();
let mut ctx = test_context();
assert_eq!(visit_expression(&input, &mut ctx), expected);
assert!(!ctx.symtab.has_symbol(ast_path("a").inner));
}
#[test]
fn if_expressions_work() {
let input = ast::Expression::If(
Box::new(ast::Expression::int_literal_signed(0).nowhere()),
Box::new(
ast::Expression::Block(Box::new(ast::Block {
statements: vec![],
result: Some(ast::Expression::int_literal_signed(1).nowhere()),
}))
.nowhere(),
),
Box::new(
ast::Expression::Block(Box::new(ast::Block {
statements: vec![],
result: Some(ast::Expression::int_literal_signed(2).nowhere()),
}))
.nowhere(),
),
);
let expected = hir::ExprKind::If(
Box::new(hir::ExprKind::int_literal(0).idless().nowhere()),
Box::new(
hir::ExprKind::Block(Box::new(hir::Block {
statements: vec![],
result: Some(hir::ExprKind::int_literal(1).idless().nowhere()),
}))
.idless()
.nowhere(),
),
Box::new(
hir::ExprKind::Block(Box::new(hir::Block {
statements: vec![],
result: Some(hir::ExprKind::int_literal(2).idless().nowhere()),
}))
.idless()
.nowhere(),
),
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context(),), expected);
}
#[test]
fn match_expressions_work() {
let input = ast::Expression::Match(
Box::new(ast::Expression::int_literal_signed(1).nowhere()),
vec![(
ast::Pattern::name("x"),
ast::Expression::int_literal_signed(2).nowhere(),
)]
.nowhere(),
);
let expected = hir::ExprKind::Match(
Box::new(hir::ExprKind::int_literal(1).idless().nowhere()),
vec![(
hir::PatternKind::name(name_id(0, "x")).idless().nowhere(),
hir::ExprKind::int_literal(2).idless().nowhere(),
)],
)
.idless();
assert_eq!(visit_expression(&input, &mut test_context(),), expected)
}
#[test]
fn match_expressions_with_enum_members_works() {
let input = ast::Expression::Match(
Box::new(ast::Expression::int_literal_signed(1).nowhere()),
vec![(
ast::Pattern::Type(
ast_path("x"),
ast::ArgumentPattern::Positional(vec![
ast::Pattern::Path(ast_path("y")).nowhere()
])
.nowhere(),
)
.nowhere(),
ast::Expression::Identifier(ast_path("y")).nowhere(),
)]
.nowhere(),
);
let expected = hir::ExprKind::Match(
Box::new(hir::ExprKind::int_literal(1).idless().nowhere()),
vec![(
hir::PatternKind::Type(
name_id(100, "x"),
vec![hir::PatternArgument {
target: ast_ident("x"),
value: hir::PatternKind::name(name_id(0, "y")).idless().nowhere(),
kind: hir::ArgumentKind::Positional,
}],
)
.idless()
.nowhere(),
hir::ExprKind::Identifier(name_id(0, "y").inner)
.idless()
.nowhere(),
)],
)
.idless();
let mut symtab = SymbolTable::new();
let enum_variant = EnumVariant {
name: Identifier("".to_string()).nowhere(),
output_type: hir::TypeSpec::unit().nowhere(),
option: 0,
params: hparams![("x", hir::TypeSpec::unit().nowhere())].nowhere(),
type_params: vec![],
documentation: "".to_string(),
}
.nowhere();
symtab.add_thing_with_id(100, ast_path("x").inner, Thing::EnumVariant(enum_variant));
assert_eq!(
visit_expression(
&input,
&mut Context {
symtab,
..test_context()
}
),
expected
)
}
#[test]
fn match_expressions_with_0_argument_enum_works() {
let input = ast::Expression::Match(
Box::new(ast::Expression::int_literal_signed(1).nowhere()),
vec![(
ast::Pattern::Type(
ast_path("x"),
ast::ArgumentPattern::Positional(vec![
ast::Pattern::Path(ast_path("y")).nowhere()
])
.nowhere(),
)
.nowhere(),
ast::Expression::Identifier(ast_path("y")).nowhere(),
)]
.nowhere(),
);
let expected = hir::ExprKind::Match(
Box::new(hir::ExprKind::int_literal(1).idless().nowhere()),
vec![(
hir::PatternKind::Type(
name_id(100, "x"),
vec![hir::PatternArgument {
target: ast_ident("x"),
value: hir::PatternKind::name(name_id(0, "y")).idless().nowhere(),
kind: hir::ArgumentKind::Positional,
}],
)
.idless()
.nowhere(),
hir::ExprKind::Identifier(name_id(0, "y").inner)
.idless()
.nowhere(),
)],
)
.idless();
let mut symtab = SymbolTable::new();
let enum_variant = EnumVariant {
name: Identifier("".to_string()).nowhere(),
output_type: hir::TypeSpec::unit().nowhere(),
option: 0,
params: hparams![("x", hir::TypeSpec::unit().nowhere())].nowhere(),
type_params: vec![],
documentation: "".to_string(),
}
.nowhere();
symtab.add_thing_with_id(100, ast_path("x").inner, Thing::EnumVariant(enum_variant));
assert_eq!(
visit_expression(
&input,
&mut Context {
symtab,
..test_context()
}
),
expected
)
}
#[test]
fn entity_instantiation_works() {
let input = ast::Expression::Call {
kind: ast::CallKind::Entity(().nowhere()),
callee: ast_path("test"),
args: ast::ArgumentList::Positional(vec![
ast::Expression::int_literal_signed(1).nowhere(),
ast::Expression::int_literal_signed(2).nowhere(),
])
.nowhere(),
turbofish: None,
}
.nowhere();
let expected = hir::ExprKind::Call {
kind: hir::expression::CallKind::Entity(().nowhere()),
callee: name_id(0, "test"),
args: hir::ArgumentList::Positional(vec![
hir::ExprKind::int_literal(1).idless().nowhere(),
hir::ExprKind::int_literal(2).idless().nowhere(),
])
.nowhere(),
turbofish: None,
safety: Safety::Default,
}
.idless();
let mut symtab = SymbolTable::new();
symtab.add_thing(
ast_path("test").inner,
Thing::Unit(
hir::UnitHead {
name: Identifier("".to_string()).nowhere(),
is_nonstatic_method: false,
inputs: hparams![
("a", hir::TypeSpec::unit().nowhere()),
("b", hir::TypeSpec::unit().nowhere()),
]
.nowhere(),
output_type: None,
unit_type_params: vec![],
scope_type_params: vec![],
unit_kind: hir::UnitKind::Entity.nowhere(),
where_clauses: vec![],
unsafe_marker: None,
documentation: "".to_string(),
}
.nowhere(),
),
);
assert_eq!(
visit_expression(
&input,
&mut Context {
symtab,
..test_context()
}
),
expected
);
}
#[test]
fn entity_instantiation_with_named_args_works() {
let input = ast::Expression::Call {
kind: ast::CallKind::Entity(().nowhere()),
callee: ast_path("test"),
args: ast::ArgumentList::Named(vec![
ast::NamedArgument::Full(
ast_ident("b"),
ast::Expression::int_literal_signed(2).nowhere(),
),
ast::NamedArgument::Full(
ast_ident("a"),
ast::Expression::int_literal_signed(1).nowhere(),
),
])
.nowhere(),
turbofish: None,
}
.nowhere();
let expected = hir::ExprKind::Call {
kind: hir::expression::CallKind::Entity(().nowhere()),
callee: name_id(0, "test"),
args: hir::ArgumentList::Named(vec![
hir::expression::NamedArgument::Full(
ast_ident("b"),
hir::ExprKind::int_literal(2).idless().nowhere(),
),
hir::expression::NamedArgument::Full(
ast_ident("a"),
hir::ExprKind::int_literal(1).idless().nowhere(),
),
])
.nowhere(),
turbofish: None,
safety: Safety::Default,
}
.idless();
let mut symtab = SymbolTable::new();
symtab.add_thing(
ast_path("test").inner,
Thing::Unit(
hir::UnitHead {
name: Identifier("".to_string()).nowhere(),
is_nonstatic_method: false,
inputs: hparams![
("a", hir::TypeSpec::unit().nowhere()),
("b", hir::TypeSpec::unit().nowhere()),
]
.nowhere(),
output_type: None,
unit_type_params: vec![],
scope_type_params: vec![],
unit_kind: hir::UnitKind::Entity.nowhere(),
where_clauses: vec![],
unsafe_marker: None,
documentation: "".to_string(),
}
.nowhere(),
),
);
assert_eq!(
visit_expression(
&input,
&mut Context {
symtab,
..test_context()
}
),
expected
);
}
#[test]
fn function_instantiation_works() {
let input = ast::Expression::Call {
kind: ast::CallKind::Function,
callee: ast_path("test"),
args: ast::ArgumentList::Positional(vec![
ast::Expression::int_literal_signed(1).nowhere(),
ast::Expression::int_literal_signed(2).nowhere(),
])
.nowhere(),
turbofish: None,
}
.nowhere();
let expected = hir::ExprKind::Call {
kind: hir::expression::CallKind::Function,
callee: name_id(0, "test"),
args: hir::ArgumentList::Positional(vec![
hir::ExprKind::int_literal(1).idless().nowhere(),
hir::ExprKind::int_literal(2).idless().nowhere(),
])
.nowhere(),
turbofish: None,
safety: Safety::Default,
}
.idless();
let mut symtab = SymbolTable::new();
symtab.add_thing(
ast_path("test").inner,
Thing::Unit(
hir::UnitHead {
name: Identifier("".to_string()).nowhere(),
is_nonstatic_method: false,
inputs: hparams![
("a", hir::TypeSpec::unit().nowhere()),
("b", hir::TypeSpec::unit().nowhere()),
]
.nowhere(),
output_type: None,
unit_type_params: vec![],
scope_type_params: vec![],
unit_kind: hir::UnitKind::Function(hir::FunctionKind::Fn).nowhere(),
where_clauses: vec![],
unsafe_marker: None,
documentation: "".to_string(),
}
.nowhere(),
),
);
assert_eq!(
visit_expression(
&input,
&mut Context {
symtab,
..test_context()
}
),
expected
);
}
}
#[cfg(test)]
mod pattern_visiting {
use crate::testutil::test_context;
use ast::{
testutil::{ast_ident, ast_path},
ArgumentPattern,
};
use hir::{
hparams,
symbol_table::{StructCallable, TypeDeclKind},
PatternKind,
};
use spade_common::name::testutil::name_id;
use super::*;
#[test]
fn bool_patterns_work() {
let input = ast::Pattern::Bool(true);
let result = visit_pattern(&input, &mut test_context());
assert_eq!(result, Ok(PatternKind::Bool(true).idless()));
}
#[test]
fn int_patterns_work() {
let input = ast::Pattern::integer(5);
let result = visit_pattern(&input, &mut test_context());
assert_eq!(result, Ok(PatternKind::integer(5).idless()));
}
#[test]
fn named_struct_patterns_work() {
let input = ast::Pattern::Type(
ast_path("a"),
ArgumentPattern::Named(vec![
(ast_ident("x"), None),
(ast_ident("y"), Some(ast::Pattern::integer(0).nowhere())),
])
.nowhere(),
);
let mut symtab = SymbolTable::new();
let type_name = symtab.add_type(
ast_path("a").inner,
TypeSymbol::Declared(vec![], TypeDeclKind::normal_struct()).nowhere(),
);
symtab.add_thing_with_name_id(
type_name.clone(),
Thing::Struct(
StructCallable {
name: Identifier("".to_string()).nowhere(),
self_type: hir::TypeSpec::Declared(type_name.clone().nowhere(), vec![])
.nowhere(),
params: hparams![
("x", hir::TypeSpec::unit().nowhere()),
("y", hir::TypeSpec::unit().nowhere()),
]
.nowhere(),
type_params: vec![],
}
.nowhere(),
),
);
let result = visit_pattern(
&input,
&mut Context {
symtab,
..test_context()
},
);
let expected = PatternKind::Type(
type_name.nowhere(),
vec![
hir::PatternArgument {
target: ast_ident("x"),
value: hir::PatternKind::name(name_id(1, "x")).idless().nowhere(),
kind: hir::ArgumentKind::ShortNamed,
},
hir::PatternArgument {
target: ast_ident("y"),
value: hir::PatternKind::integer(0).idless().nowhere(),
kind: hir::ArgumentKind::Named,
},
],
)
.idless();
assert_eq!(result, Ok(expected))
}
}
#[cfg(test)]
mod register_visiting {
use super::*;
use crate::testutil::test_context;
use spade_ast::testutil::{ast_ident, ast_path};
use spade_common::location_info::WithLocation;
use spade_common::name::testutil::name_id;
#[test]
fn register_visiting_works() {
let input = ast::Register {
pattern: ast::Pattern::name("test"),
clock: ast::Expression::Identifier(ast_path("clk")).nowhere(),
reset: Some((
ast::Expression::Identifier(ast_path("rst")).nowhere(),
ast::Expression::int_literal_signed(0).nowhere(),
)),
initial: Some(ast::Expression::int_literal_signed(0).nowhere()),
value: ast::Expression::int_literal_signed(1).nowhere(),
value_type: Some(ast::TypeSpec::Tuple(Vec::new()).nowhere()),
attributes: ast::AttributeList::empty(),
}
.nowhere();
let expected = hir::Register {
pattern: hir::PatternKind::name(name_id(2, "test"))
.idless()
.nowhere(),
clock: hir::ExprKind::Identifier(name_id(0, "clk").inner)
.idless()
.nowhere(),
reset: Some((
hir::ExprKind::Identifier(name_id(1, "rst").inner)
.idless()
.nowhere(),
hir::ExprKind::int_literal(0).idless().nowhere(),
)),
initial: Some(hir::ExprKind::int_literal(0).idless().nowhere()),
value: hir::ExprKind::int_literal(1).idless().nowhere(),
value_type: Some(hir::TypeSpec::unit().nowhere()),
attributes: hir::AttributeList::empty(),
};
let mut symtab = SymbolTable::new();
let clk_id = symtab.add_local_variable(ast_ident("clk"));
assert_eq!(clk_id.0, 0);
let rst_id = symtab.add_local_variable(ast_ident("rst"));
assert_eq!(rst_id.0, 1);
assert_eq!(
visit_register(
&input,
&mut Context {
symtab,
..test_context()
}
),
Ok(vec![hir::Statement::Register(expected).nowhere()])
);
}
}
#[cfg(test)]
mod item_visiting {
use super::*;
use ast::aparams;
use spade_ast::testutil::ast_ident;
use spade_common::location_info::WithLocation;
use spade_common::name::testutil::name_id;
use crate::testutil::test_context;
use pretty_assertions::assert_eq;
#[test]
pub fn item_entity_visiting_works() {
let input = ast::Item::Unit(
ast::Unit {
head: ast::UnitHead {
unsafe_token: None,
extern_token: None,
name: ast_ident("test"),
output_type: None,
inputs: aparams![],
type_params: None,
attributes: ast::AttributeList(vec![]),
unit_kind: ast::UnitKind::Entity.nowhere(),
where_clauses: vec![],
},
body: Some(
ast::Expression::Block(Box::new(ast::Block {
statements: vec![],
result: Some(ast::Expression::int_literal_signed(0).nowhere()),
}))
.nowhere(),
),
}
.nowhere(),
);
let expected = hir::Item::Unit(
hir::Unit {
name: hir::UnitName::FullPath(name_id(0, "test")),
head: hir::UnitHead {
name: Identifier("test".to_string()).nowhere(),
is_nonstatic_method: false,
output_type: None,
inputs: hir::ParameterList(vec![]).nowhere(),
unit_type_params: vec![],
scope_type_params: vec![],
unit_kind: hir::UnitKind::Entity.nowhere(),
where_clauses: vec![],
unsafe_marker: None,
documentation: "".to_string(),
},
attributes: hir::AttributeList::empty(),
inputs: vec![],
body: hir::ExprKind::Block(Box::new(hir::Block {
statements: vec![],
result: Some(hir::ExprKind::int_literal(0).idless().nowhere()),
}))
.idless()
.nowhere(),
}
.nowhere(),
);
let mut ctx = test_context();
global_symbols::visit_item(&input, &mut ctx).unwrap();
assert_eq!(visit_item(&input, &mut ctx), Ok(vec![expected]));
}
}
#[cfg(test)]
mod module_visiting {
use std::collections::HashMap;
use super::*;
use hir::hparams;
use spade_ast::testutil::ast_ident;
use spade_common::location_info::WithLocation;
use spade_common::name::testutil::name_id;
use spade_hir::impl_tab::ImplTab;
use crate::testutil::test_context;
use pretty_assertions::assert_eq;
use spade_common::namespace::ModuleNamespace;
#[test]
fn visiting_module_with_one_entity_works() {
let input = ast::ModuleBody {
members: vec![ast::Item::Unit(
ast::Unit {
head: ast::UnitHead {
unsafe_token: None,
extern_token: None,
name: ast_ident("test"),
output_type: None,
inputs: ParameterList::without_self(vec![]).nowhere(),
type_params: None,
attributes: ast::AttributeList(vec![]),
unit_kind: ast::UnitKind::Entity.nowhere(),
where_clauses: vec![],
},
body: Some(
ast::Expression::Block(Box::new(ast::Block {
statements: vec![],
result: Some(ast::Expression::int_literal_signed(0).nowhere()),
}))
.nowhere(),
),
}
.nowhere(),
)],
documentation: vec![],
};
let expected = hir::ItemList {
executables: vec![(
name_id(0, "test").inner,
hir::ExecutableItem::Unit(
hir::Unit {
name: hir::UnitName::FullPath(name_id(0, "test")),
head: hir::UnitHead {
name: Identifier("test".to_string()).nowhere(),
is_nonstatic_method: false,
output_type: None,
inputs: hparams!().nowhere(),
unit_type_params: vec![],
scope_type_params: vec![],
unit_kind: hir::UnitKind::Entity.nowhere(),
where_clauses: vec![],
unsafe_marker: None,
documentation: "".to_string(),
},
inputs: vec![],
attributes: hir::AttributeList::empty(),
body: hir::ExprKind::Block(Box::new(hir::Block {
statements: vec![],
result: Some(hir::ExprKind::int_literal(0).idless().nowhere()),
}))
.idless()
.nowhere(),
}
.nowhere(),
),
)]
.into_iter()
.collect(),
types: vec![].into_iter().collect(),
modules: vec![].into_iter().collect(),
traits: HashMap::new(),
impls: ImplTab::new(),
};
let mut ctx = test_context();
global_symbols::gather_symbols(&input, &mut ctx).expect("failed to collect global symbols");
assert_eq!(visit_module_body(&input, &mut ctx), Ok(()));
assert_eq!(ctx.item_list, expected);
}
#[test]
fn visiting_submodules_works() {
let input = ast::ModuleBody {
members: vec![ast::Item::Module(
ast::Module {
name: ast_ident("outer"),
body: ast::ModuleBody {
members: vec![ast::Item::Module(
ast::Module {
name: ast_ident("inner"),
body: ast::ModuleBody {
members: vec![],
documentation: vec![],
}
.nowhere(),
}
.nowhere(),
)],
documentation: vec![],
}
.nowhere(),
}
.nowhere(),
)],
documentation: vec![],
};
let expected = hir::ItemList {
executables: vec![].into_iter().collect(),
types: vec![].into_iter().collect(),
modules: vec![
(
name_id(1, "outer").inner,
hir::Module {
name: name_id(1, "outer"),
documentation: "".to_string(),
},
),
(
name_id(2, "outer::inner").inner,
hir::Module {
name: name_id(2, "outer::inner"),
documentation: "".to_string(),
},
),
]
.into_iter()
.collect(),
traits: HashMap::new(),
impls: ImplTab::new(),
};
let mut ctx = test_context();
let namespace = ModuleNamespace {
namespace: Path::from_strs(&[""]),
base_namespace: Path::from_strs(&[""]),
file: "test/file.spade".to_string(),
};
ctx.symtab.add_thing(
namespace.namespace.clone(),
Thing::Module(namespace.namespace.0[0].clone()),
);
global_symbols::gather_types(&input, &mut ctx).expect("failed to collect types");
assert_eq!(visit_module_body(&input, &mut ctx), Ok(()));
assert_eq!(ctx.item_list, expected);
}
}