use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use miette::{Diagnostic, NamedSource, SourceSpan};
use petgraph::graph::DiGraph;
use petgraph::graph::NodeIndex;
use thiserror::Error;
use crate::parse::*;
use crate::spanned::*;
use crate::Compiler;
use crate::Result;
#[derive(Debug, Error, Diagnostic)]
#[error("{message}")]
pub struct KdlScriptTypeError {
pub message: String,
#[source_code]
pub src: Arc<NamedSource>,
#[label]
pub span: SourceSpan,
#[diagnostic(help)]
pub help: Option<String>,
}
#[derive(Debug)]
pub struct TypedProgram {
tcx: TyCtx,
funcs: Vec<Func>,
builtin_funcs_start: usize,
}
pub type TyIdx = usize;
pub type FuncIdx = usize;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ty {
Primitive(PrimitiveTy),
Struct(StructTy),
Union(UnionTy),
Enum(EnumTy),
Tagged(TaggedTy),
Alias(AliasTy),
Pun(PunTy),
Array(ArrayTy),
Ref(RefTy),
Empty,
}
impl Ty {
pub fn is_nominal(&self) -> bool {
match self {
Ty::Primitive(_) => false,
Ty::Struct(_) => true,
Ty::Union(_) => true,
Ty::Enum(_) => true,
Ty::Tagged(_) => true,
Ty::Alias(_) => true,
Ty::Pun(_) => true,
Ty::Array(_) => false,
Ty::Ref(_) => false,
Ty::Empty => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Func {
pub name: Ident,
pub inputs: Vec<Arg>,
pub outputs: Vec<Arg>,
pub attrs: Vec<Attr>,
#[cfg(feature = "eval")]
pub body: (),
}
#[derive(Debug, Clone)]
pub struct Arg {
pub name: Ident,
pub ty: TyIdx,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum PrimitiveTy {
I8,
I16,
I32,
I64,
I128,
I256,
U8,
U16,
U32,
U64,
U128,
U256,
F16,
F32,
F64,
F128,
Bool,
Ptr,
}
pub const PRIMITIVES: &[(&str, PrimitiveTy)] = &[
("i8", PrimitiveTy::I8),
("i16", PrimitiveTy::I16),
("i32", PrimitiveTy::I32),
("i64", PrimitiveTy::I64),
("i128", PrimitiveTy::I128),
("i256", PrimitiveTy::I256),
("u8", PrimitiveTy::U8),
("u16", PrimitiveTy::U16),
("u32", PrimitiveTy::U32),
("u64", PrimitiveTy::U64),
("u128", PrimitiveTy::U128),
("u256", PrimitiveTy::U256),
("f16", PrimitiveTy::F16),
("f32", PrimitiveTy::F32),
("f64", PrimitiveTy::F64),
("f128", PrimitiveTy::F128),
("bool", PrimitiveTy::Bool),
("ptr", PrimitiveTy::Ptr),
];
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StructTy {
pub name: Ident,
pub fields: Vec<FieldTy>,
pub attrs: Vec<Attr>,
pub all_fields_were_blank: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnionTy {
pub name: Ident,
pub fields: Vec<FieldTy>,
pub attrs: Vec<Attr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EnumTy {
pub name: Ident,
pub variants: Vec<EnumVariantTy>,
pub attrs: Vec<Attr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EnumVariantTy {
pub name: Ident,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TaggedTy {
pub name: Ident,
pub variants: Vec<TaggedVariantTy>,
pub attrs: Vec<Attr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TaggedVariantTy {
pub name: Ident,
pub fields: Option<Vec<FieldTy>>,
pub all_fields_were_blank: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AliasTy {
pub name: Ident,
pub real: TyIdx,
pub attrs: Vec<Attr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FieldTy {
pub idx: usize,
pub ident: Ident,
pub ty: TyIdx,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ArrayTy {
pub elem_ty: TyIdx,
pub len: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RefTy {
pub pointee_ty: TyIdx,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PunTy {
pub name: Ident,
pub blocks: Vec<PunBlockTy>,
pub attrs: Vec<Attr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PunBlockTy {
pub selector: PunSelector,
pub real: TyIdx,
}
#[derive(Debug)]
pub(crate) struct TyCtx {
src: Arc<NamedSource>,
tys: Vec<Ty>,
ty_map: HashMap<Ty, TyIdx>,
ty_facts: HashMap<TyIdx, TypeFact>,
envs: Vec<CheckEnv>,
}
#[derive(Debug, Clone)]
struct TypeFact {
contains_ref: bool,
}
#[derive(Debug)]
struct CheckEnv {
tys: HashMap<Ident, TyIdx>,
}
pub fn typeck(comp: &mut Compiler, parsed: &ParsedProgram) -> Result<TypedProgram> {
let mut tcx = TyCtx {
src: comp.source.clone().unwrap(),
tys: vec![],
ty_map: HashMap::new(),
ty_facts: HashMap::new(),
envs: vec![],
};
tcx.envs.push(CheckEnv {
tys: HashMap::new(),
});
tcx.add_builtins();
tcx.envs.push(CheckEnv {
tys: HashMap::new(),
});
for (ty_name, _ty_decl) in &parsed.tys {
let _ty_idx = tcx.push_nominal_decl_incomplete(ty_name.clone());
}
for (ty_name, ty_decl) in &parsed.tys {
tcx.complete_nominal_decl(ty_name, ty_decl)?;
}
let funcs = parsed
.funcs
.iter()
.map(|(_func_name, func_decl)| -> Result<Func> {
let inputs = func_decl
.inputs
.iter()
.enumerate()
.map(|(idx, var)| -> Result<Arg> {
let name = ident_var(var.name.clone(), "arg", idx, &var.ty);
let ty = tcx.memoize_ty(&var.ty)?;
Ok(Arg { name, ty })
})
.collect::<Result<Vec<_>>>()?;
let outputs = func_decl
.outputs
.iter()
.enumerate()
.map(|(idx, var)| {
let name = ident_var(var.name.clone(), "out", idx, &var.ty);
let ty = tcx.memoize_ty(&var.ty)?;
Ok(Arg { name, ty })
})
.collect::<Result<Vec<_>>>()?;
let name = func_decl.name.clone();
let attrs = func_decl.attrs.clone();
Ok(Func {
name,
inputs,
outputs,
attrs,
body: (),
})
})
.collect::<Result<Vec<_>>>()?;
tcx.compute_ty_facts()?;
let builtin_funcs_start = parsed.builtin_funcs_start;
Ok(TypedProgram {
tcx,
funcs,
builtin_funcs_start,
})
}
impl TyCtx {
fn add_builtins(&mut self) {
let builtins = PRIMITIVES
.iter()
.map(|(name, prim)| (*name, Ty::Primitive(*prim)))
.chain(Some(("()", Ty::Empty)));
for (ty_name, ty) in builtins {
let ty_idx = self.tys.len();
self.tys.push(ty);
self.envs
.last_mut()
.unwrap()
.tys
.insert(Ident::from(ty_name.to_owned()), ty_idx);
}
}
fn push_nominal_decl_incomplete(&mut self, ty_name: Ident) -> TyIdx {
let ty_idx = self.tys.len();
let dummy_ty = Ty::Empty;
self.tys.push(dummy_ty);
self.envs.last_mut().unwrap().tys.insert(ty_name, ty_idx);
ty_idx
}
fn complete_nominal_decl(&mut self, ty_name: &Ident, ty_decl: &TyDecl) -> Result<()> {
let ty_idx = self
.resolve_nominal_ty(ty_name)
.expect("completing a nominal ty that hasn't been decl'd");
let ty = self.memoize_nominal_parts(ty_decl)?;
self.tys[ty_idx] = ty;
Ok(())
}
fn memoize_nominal_parts(&mut self, ty_decl: &TyDecl) -> Result<Ty> {
let ty = match ty_decl {
TyDecl::Struct(decl) => {
let fields = decl
.fields
.iter()
.enumerate()
.map(|(idx, f)| {
Ok(FieldTy {
idx,
ident: ident_var(f.name.clone(), "field", idx, &f.ty),
ty: self.memoize_ty(&f.ty)?,
})
})
.collect::<Result<Vec<_>>>()?;
let all_fields_were_blank = fields.iter().all(|f| f.ident.was_blank);
Ty::Struct(StructTy {
name: decl.name.clone(),
fields,
attrs: decl.attrs.clone(),
all_fields_were_blank,
})
}
TyDecl::Union(decl) => {
let fields = decl
.fields
.iter()
.enumerate()
.map(|(idx, f)| {
Ok(FieldTy {
idx,
ident: ident_var(f.name.clone(), "field", idx, &f.ty),
ty: self.memoize_ty(&f.ty)?,
})
})
.collect::<Result<Vec<_>>>()?;
Ty::Union(UnionTy {
name: decl.name.clone(),
fields,
attrs: decl.attrs.clone(),
})
}
TyDecl::Enum(decl) => {
let variants = decl
.variants
.iter()
.map(|v| EnumVariantTy {
name: v.name.clone(),
})
.collect::<Vec<_>>();
Ty::Enum(EnumTy {
name: decl.name.clone(),
variants,
attrs: decl.attrs.clone(),
})
}
TyDecl::Tagged(decl) => {
let variants = decl
.variants
.iter()
.map(|v| {
let fields = if let Some(fields) = &v.fields {
Some(
fields
.iter()
.enumerate()
.map(|(idx, f)| {
Ok(FieldTy {
idx,
ident: ident_var(f.name.clone(), "field", idx, &f.ty),
ty: self.memoize_ty(&f.ty)?,
})
})
.collect::<Result<Vec<_>>>()?,
)
} else {
None
};
let all_fields_were_blank = fields
.as_deref()
.unwrap_or_default()
.iter()
.all(|f| f.ident.was_blank);
Ok(TaggedVariantTy {
name: v.name.clone(),
fields,
all_fields_were_blank,
})
})
.collect::<Result<Vec<_>>>()?;
Ty::Tagged(TaggedTy {
name: decl.name.clone(),
variants,
attrs: decl.attrs.clone(),
})
}
TyDecl::Alias(decl) => {
let real_ty = self.memoize_ty(&decl.alias)?;
Ty::Alias(AliasTy {
name: decl.name.clone(),
real: real_ty,
attrs: decl.attrs.clone(),
})
}
TyDecl::Pun(decl) => {
let blocks = decl
.blocks
.iter()
.map(|block| {
self.envs.push(CheckEnv {
tys: HashMap::new(),
});
let real_decl = &block.decl;
let real = self.push_nominal_decl_incomplete(decl.name.clone());
self.complete_nominal_decl(&decl.name, real_decl)?;
self.envs.pop();
Ok(PunBlockTy {
selector: block.selector.clone(),
real,
})
})
.collect::<Result<Vec<_>>>()?;
Ty::Pun(PunTy {
name: decl.name.clone(),
blocks,
attrs: decl.attrs.clone(),
})
}
};
Ok(ty)
}
fn resolve_nominal_ty(&mut self, ty_name: &str) -> Option<TyIdx> {
for env in self.envs.iter_mut().rev() {
if let Some(ty) = env.tys.get(ty_name) {
return Some(*ty);
}
}
None
}
fn memoize_ty(&mut self, ty_ref: &Spanned<Tydent>) -> Result<TyIdx> {
let ty_idx = match &**ty_ref {
Tydent::Empty => self.memoize_inner(Ty::Empty),
Tydent::Ref(pointee_ty_ref) => {
let pointee_ty = self.memoize_ty(pointee_ty_ref)?;
self.memoize_inner(Ty::Ref(RefTy { pointee_ty }))
}
Tydent::Array(elem_ty_ref, len) => {
let elem_ty = self.memoize_ty(elem_ty_ref)?;
self.memoize_inner(Ty::Array(ArrayTy { elem_ty, len: *len }))
}
Tydent::Name(name) => {
if let Some(ty_idx) = self.resolve_nominal_ty(name) {
ty_idx
} else {
return Err(KdlScriptTypeError {
message: format!("use of undefined type name: {name}"),
src: self.src.clone(),
span: Spanned::span(name),
help: None,
})?;
}
}
};
Ok(ty_idx)
}
fn memoize_inner(&mut self, ty: Ty) -> TyIdx {
if let Some(idx) = self.ty_map.get(&ty) {
*idx
} else {
let ty1 = ty.clone();
let ty2 = ty;
let idx = self.tys.len();
self.ty_map.insert(ty1, idx);
self.tys.push(ty2);
idx
}
}
pub fn realize_ty(&self, ty: TyIdx) -> &Ty {
self.tys
.get(ty)
.expect("Internal Compiler Error: invalid TyIdx")
}
pub fn resolve_pun(&self, pun: &PunTy, env: &PunEnv) -> Result<TyIdx> {
for block in &pun.blocks {
if block.selector.matches(env) {
return Ok(block.real);
}
}
Err(KdlScriptTypeError {
message: "Failed to find applicable pun for this target environment".to_string(),
src: self.src.clone(),
span: Spanned::span(&pun.name),
help: Some(format!("Add another block that matches {:#?}", env)),
})?
}
fn compute_ty_facts(&mut self) -> Result<()> {
let mut to_compute = (0..self.tys.len()).collect::<Vec<TyIdx>>();
let mut already_visited = HashSet::new();
fn aggregate_facts(
this: &mut TyCtx,
to_compute: &mut Vec<TyIdx>,
already_visited: &mut HashSet<TyIdx>,
cur_ty: TyIdx,
child_tys: Vec<TyIdx>,
) -> Result<Option<TypeFact>> {
let mut facts = TypeFact {
contains_ref: false,
};
let mut missing_info = vec![];
for child_ty in child_tys {
let Some(child_fact) = this.ty_facts.get(&child_ty) else {
missing_info.push(child_ty);
continue;
};
let TypeFact { contains_ref } = child_fact;
facts.contains_ref |= contains_ref;
}
if missing_info.is_empty() {
return Ok(Some(facts));
}
if already_visited.contains(&cur_ty) {
Err(KdlScriptTypeError {
message: "This type is infinitely recursive without an indirection!".to_owned(),
src: this.src.clone(),
span: this.span_for_ty_decl(cur_ty),
help: Some("Add a reference (&) somewhere".to_owned()),
})?;
}
already_visited.insert(cur_ty);
to_compute.push(cur_ty);
to_compute.extend(missing_info);
Ok(None)
}
while let Some(ty_idx) = to_compute.pop() {
let facts = match self.realize_ty(ty_idx) {
Ty::Primitive(_) => Some(TypeFact {
contains_ref: false,
}),
Ty::Empty => Some(TypeFact {
contains_ref: false,
}),
Ty::Enum(_) => Some(TypeFact {
contains_ref: false,
}),
Ty::Ref(_) => Some(TypeFact { contains_ref: true }),
Ty::Alias(ty) => {
let child_tys = vec![ty.real];
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
Ty::Array(ty) => {
let child_tys = vec![ty.elem_ty];
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
Ty::Struct(ty) => {
let child_tys = ty.fields.iter().map(|f| f.ty).collect();
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
Ty::Union(ty) => {
let child_tys = ty.fields.iter().map(|f| f.ty).collect();
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
Ty::Tagged(ty) => {
let child_tys = ty
.variants
.iter()
.flat_map(|v| v.fields.as_deref().unwrap_or_default().iter().map(|f| f.ty))
.collect();
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
Ty::Pun(ty) => {
let child_tys = ty.blocks.iter().map(|f| f.real).collect();
aggregate_facts(
self,
&mut to_compute,
&mut already_visited,
ty_idx,
child_tys,
)?
}
};
if let Some(facts) = facts {
self.ty_facts.insert(ty_idx, facts);
}
}
Ok(())
}
pub fn span_for_ty_decl(&self, ty: TyIdx) -> SourceSpan {
match self.realize_ty(ty) {
Ty::Primitive(_) => SourceSpan::from(1..1),
Ty::Empty => SourceSpan::from(1..1),
Ty::Struct(ty) => Spanned::span(&ty.name),
Ty::Union(ty) => Spanned::span(&ty.name),
Ty::Enum(ty) => Spanned::span(&ty.name),
Ty::Tagged(ty) => Spanned::span(&ty.name),
Ty::Alias(ty) => Spanned::span(&ty.name),
Ty::Pun(ty) => Spanned::span(&ty.name),
Ty::Array(ty) => self.span_for_ty_decl(ty.elem_ty),
Ty::Ref(ty) => self.span_for_ty_decl(ty.pointee_ty),
}
}
pub fn format_ty(&self, ty: TyIdx) -> String {
match self.realize_ty(ty) {
Ty::Primitive(prim) => format!("{:?}", prim).to_lowercase(),
Ty::Empty => "()".to_string(),
Ty::Struct(decl) => format!("{}", decl.name),
Ty::Enum(decl) => format!("{}", decl.name),
Ty::Tagged(decl) => format!("{}", decl.name),
Ty::Union(decl) => format!("{}", decl.name),
Ty::Alias(decl) => format!("{}", decl.name),
Ty::Pun(decl) => format!("{}", decl.name),
Ty::Array(array_ty) => {
let inner = self.format_ty(array_ty.elem_ty);
format!("[{}; {}]", inner, array_ty.len)
}
Ty::Ref(ref_ty) => {
let inner = self.format_ty(ref_ty.pointee_ty);
format!("&{}", inner)
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum DefinitionGraphNode {
Func(FuncIdx),
Ty(TyIdx),
}
#[derive(Debug, Clone)]
pub struct DefinitionGraph {
graph: DiGraph<DefinitionGraphNode, ()>,
func_nodes: Vec<NodeIndex>,
def_order: Vec<Vec<NodeIndex>>,
}
impl TypedProgram {
pub fn realize_ty(&self, ty: TyIdx) -> &Ty {
self.tcx.realize_ty(ty)
}
pub fn realize_func(&self, func: FuncIdx) -> &Func {
&self.funcs[func]
}
pub fn ty_contains_ref(&self, ty: TyIdx) -> bool {
self.tcx.ty_facts[&ty].contains_ref
}
pub fn all_funcs(&self) -> impl Iterator<Item = FuncIdx> {
0..self.builtin_funcs_start
}
pub fn resolve_pun(&self, pun: &PunTy, env: &PunEnv) -> Result<TyIdx> {
self.tcx.resolve_pun(pun, env)
}
pub fn format_ty(&self, ty: TyIdx) -> String {
self.tcx.format_ty(ty)
}
pub fn definition_graph(&self, env: &PunEnv) -> Result<DefinitionGraph> {
let mut graph = petgraph::graph::DiGraph::new();
let mut nodes = vec![];
for (ty_idx, _ty) in self.tcx.tys.iter().enumerate() {
let ty_node = graph.add_node(DefinitionGraphNode::Ty(ty_idx));
nodes.push(ty_node);
}
for (ty_idx, ty) in self.tcx.tys.iter().enumerate() {
let ty_node = nodes[ty_idx];
match ty {
Ty::Struct(ty) => {
for field in &ty.fields {
let field_ty_node = nodes[field.ty];
graph.update_edge(ty_node, field_ty_node, ());
}
}
Ty::Union(ty) => {
for field in &ty.fields {
let field_ty_node = nodes[field.ty];
graph.update_edge(ty_node, field_ty_node, ());
}
}
Ty::Tagged(ty) => {
for variant in &ty.variants {
if let Some(fields) = &variant.fields {
for field in fields {
let field_ty_node = nodes[field.ty];
graph.update_edge(ty_node, field_ty_node, ());
}
}
}
}
Ty::Alias(ty) => {
let real_ty_node = nodes[ty.real];
graph.update_edge(ty_node, real_ty_node, ());
}
Ty::Pun(ty) => {
let real_ty_node = nodes[self.tcx.resolve_pun(ty, env)?];
graph.update_edge(ty_node, real_ty_node, ());
}
Ty::Array(ty) => {
let elem_ty_node = nodes[ty.elem_ty];
graph.update_edge(ty_node, elem_ty_node, ());
}
Ty::Ref(ty) => {
let pointee_ty_node = nodes[ty.pointee_ty];
graph.update_edge(ty_node, pointee_ty_node, ());
}
Ty::Enum(_) => {
}
Ty::Primitive(_) | Ty::Empty => {
}
}
}
let mut func_nodes = vec![];
for (func_idx, func) in self.funcs.iter().enumerate() {
let func_node = graph.add_node(DefinitionGraphNode::Func(func_idx));
for arg in func.inputs.iter().chain(func.outputs.iter()) {
let arg_ty_node = nodes[arg.ty];
graph.update_edge(func_node, arg_ty_node, ());
}
func_nodes.push(func_node);
}
let mut def_order = petgraph::algo::kosaraju_scc(&graph);
for component in &mut def_order {
let mut sorted_nodes = std::mem::take(component);
sorted_nodes.sort_by(|&lhs, &rhs| match (&graph[lhs], &graph[rhs]) {
(DefinitionGraphNode::Func(l), DefinitionGraphNode::Func(r)) => l.cmp(r),
(DefinitionGraphNode::Func(_), DefinitionGraphNode::Ty(_)) => Ordering::Less,
(DefinitionGraphNode::Ty(_), DefinitionGraphNode::Func(_)) => Ordering::Greater,
(DefinitionGraphNode::Ty(l), DefinitionGraphNode::Ty(r)) => {
let lty = self.realize_ty(*l);
let rty = self.realize_ty(*r);
rty.is_nominal().cmp(<y.is_nominal()).then(l.cmp(r))
}
});
*component = sorted_nodes;
}
Ok(DefinitionGraph {
graph,
func_nodes,
def_order,
})
}
}
pub enum Definition {
DeclareTy(TyIdx),
DefineTy(TyIdx),
DeclareFunc(FuncIdx),
DefineFunc(FuncIdx),
}
impl DefinitionGraph {
pub fn definitions(&self, funcs: impl IntoIterator<Item = FuncIdx>) -> Vec<Definition> {
let mut reachable = std::collections::HashSet::new();
petgraph::visit::depth_first_search(
&self.graph,
funcs.into_iter().map(|f| self.func_nodes[f]),
|event| {
if let petgraph::visit::DfsEvent::Discover(node, _) = event {
reachable.insert(node);
}
},
);
let mut output = vec![];
for component in &self.def_order {
let nodes = component.iter().rev().filter(|n| reachable.contains(*n));
for &node_idx in nodes.clone().skip(1) {
let node = &self.graph[node_idx];
match *node {
DefinitionGraphNode::Func(func_idx) => {
output.push(Definition::DeclareFunc(func_idx));
}
DefinitionGraphNode::Ty(ty_idx) => {
output.push(Definition::DeclareTy(ty_idx));
}
}
}
for &node_idx in nodes {
let node = &self.graph[node_idx];
match *node {
DefinitionGraphNode::Func(func_idx) => {
output.push(Definition::DefineFunc(func_idx));
}
DefinitionGraphNode::Ty(ty_idx) => {
output.push(Definition::DefineTy(ty_idx));
}
}
}
}
output
}
}
fn ident_var<T>(val: Option<Ident>, basename: &str, idx: usize, backup_span: &Spanned<T>) -> Ident {
if let Some(val) = val {
val
} else {
let val = format!("{basename}{idx}");
let val = Spanned::new(val, Spanned::span(backup_span));
Ident {
was_blank: false,
val,
}
}
}