/// An error from lowering the AST to the HIR. #[derive(Debug)] #[non_exhaustive] pubenum LoweringError { /// The purpose of having this is that translating to the HIR has enormous /// potential for really detailed error handling and giving suggestions. /// /// Unfortunately, working out what the error enum should look like to enable /// this is really hard. The plan is that once the lowering code is completely /// written, we ctrl+F for `"LoweringError::Other"` in the lowering code, and turn every /// instance into an specialized enum variant, generalizing where possible /// without losing any information.
Other(String),
}
/// An error store, which one can push errors to. It keeps track of the /// current "context" for the error, usually a type or a type::method. `'tree` /// is the AST/HIR tree it is temporarily borrowing from for the context. #[derive(Default)] pubstruct ErrorStore<'tree> { /// The errors
errors: Vec<ErrorAndContext>, /// The current context (types, modules)
item: &'tree str, /// The current sub-item context (methods, etc)
subitem: Option<&'tree str>,
}
/// An item and the info needed to pub(crate) struct ItemAndInfo<'ast, Ast> { pub(crate) item: &'ast Ast, pub(crate) in_path: &'ast ast::Path, /// Any parent attributes resolved from the module, for a type context pub(crate) ty_parent_attrs: Attrs,
/// Any parent attributes resolved from the module, for a method context pub(crate) method_parent_attrs: Attrs, pub(crate) id: TypeId,
}
impl<'ast> LoweringContext<'ast> { /// Lowers an [`ast::Ident`]s into an [`hir::IdentBuf`]. /// /// If there are any errors, they're pushed to `errors` and `Err` is returned. pub(super) fn lower_ident(
&mutself,
ident: &ast::Ident,
context: &'static str,
) -> Result<IdentBuf, ()> { match ident.as_str().ck() {
Ok(name) => Ok(name.to_owned()),
Err(e) => { self.errors.push(LoweringError::Other(format!( "Ident `{ident}` from {context} could not be turned into a Rust ident: {e}"
)));
Err(())
}
}
}
fn lower_struct(&mutself, item: ItemAndInfo<'ast, ast::Struct>) -> Result<StructDef, ()> { let ast_struct = item.item; self.errors.set_item(ast_struct.name.as_str()); let name = self.lower_ident(&ast_struct.name, "struct name");
let fields = if ast_struct.fields.is_empty() { self.errors.push(LoweringError::Other(format!( "struct `{}` is a ZST because it has no fields",
ast_struct.name
)));
Err(())
} else { letmut fields = Ok(Vec::with_capacity(ast_struct.fields.len()));
for (name, ty, docs) in ast_struct.fields.iter() { let name = self.lower_ident(name, "struct field name"); let ty = self.lower_type(ty, &mut &ast_struct.lifetimes, item.in_path);
fn lower_out_struct(
&mutself,
item: ItemAndInfo<'ast, ast::Struct>,
) -> Result<OutStructDef, ()> { let ast_out_struct = item.item; self.errors.set_item(ast_out_struct.name.as_str()); let name = self.lower_ident(&ast_out_struct.name, "out-struct name");
let fields = if ast_out_struct.fields.is_empty() { self.errors.push(LoweringError::Other(format!( "struct `{}` is a ZST because it has no fields",
ast_out_struct.name
)));
Err(())
} else { letmut fields = Ok(Vec::with_capacity(ast_out_struct.fields.len()));
for (name, ty, docs) in ast_out_struct.fields.iter() { let name = self.lower_ident(name, "out-struct field name"); let ty = self.lower_out_type(ty, &mut &ast_out_struct.lifetimes, item.in_path, true);
/// Lowers an [`ast::Method`]s an [`hir::Method`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_method(
&mutself,
method: &'ast ast::Method,
in_path: &ast::Path,
method_parent_attrs: &Attrs,
self_id: TypeId,
special_method_presence: &mut SpecialMethodPresence,
) -> Result<Method, ()> { self.errors.set_subitem(method.name.as_str()); let name = self.lower_ident(&method.name, "method name");
let (ast_params, takes_writeable) = match method.params.split_last() {
Some((last, remaining)) if last.is_writeable() => (remaining, true),
_ => (&method.params[..], false),
};
let self_param_ltl = SelfParamLifetimeLowerer::new(&method.lifetime_env, self)?;
let is_comparison = matches!(
hir_method.attrs.special_method,
Some(SpecialMethod::Comparison)
); if is_comparison && method.return_type != Some(ast::TypeName::Ordering) { self.errors.push(LoweringError::Other( "Found comparison method that does not return cmp::Ordering".into(),
)); return Err(());
}
Ok(hir_method)
}
/// Lowers many [`ast::Method`]s into a vector of [`hir::Method`]s. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_all_methods(
&mutself,
ast_methods: &'ast [ast::Method],
in_path: &ast::Path,
method_parent_attrs: &Attrs,
self_id: TypeId,
special_method_presence: &mut SpecialMethodPresence,
) -> Result<Vec<Method>, ()> { letmut methods = Ok(Vec::with_capacity(ast_methods.len()));
for method in ast_methods { let method = self.lower_method(
method,
in_path,
method_parent_attrs,
self_id,
special_method_presence,
); match (method, &mut methods) {
(Ok(method), Ok(methods)) => {
methods.push(method);
}
_ => methods = Err(()),
}
}
methods
}
/// Lowers an [`ast::TypeName`]s into a [`hir::Type`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_type(
&mutself,
ty: &ast::TypeName,
ltl: &mutimpl LifetimeLowerer,
in_path: &ast::Path,
) -> Result<Type, ()> { match ty {
ast::TypeName::Primitive(prim) => Ok(Type::Primitive(PrimitiveType::from_ast(*prim))),
ast::TypeName::Ordering => { self.errors.push(LoweringError::Other("Found cmp::Ordering in parameter or struct field, it is only allowed in return types".to_string()));
Err(())
}
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Struct(strct) => { iflet Some(tcx_id) = self.lookup_id.resolve_struct(strct) { let lifetimes = ltl.lower_generics(
&path.lifetimes[..],
&strct.lifetimes,
ty.is_self(),
);
Ok(Type::Struct(StructPath::new(lifetimes, tcx_id)))
} elseifself.lookup_id.resolve_out_struct(strct).is_some() { self.errors.push(LoweringError::Other(format!("found struct in input that is marked with #[diplomat::out]: {ty} in {path}")));
Err(())
} else {
unreachable!("struct `{}` wasn't found in the set of structs or out-structs, this is a bug.", strct.name);
}
}
ast::CustomType::Opaque(_) => { self.errors.push(LoweringError::Other(format!( "Opaque passed by value: {path}"
)));
Err(())
}
ast::CustomType::Enum(enm) => { let tcx_id = self.lookup_id.resolve_enum(enm).expect( "can't find enum in lookup map, which contains all enums from env",
);
Ok(Type::Enum(EnumPath::new(tcx_id)))
}
}
}
ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(opaque) => { let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability); let lifetimes = ltl.lower_generics(
&path.lifetimes[..],
&opaque.lifetimes,
ref_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(Type::Opaque(OpaquePath::new(
lifetimes,
Optional(false),
borrow,
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found &T in input where T is a custom type, but not opaque. T = {ref_ty}")));
Err(())
}
}
}
_ => { self.errors.push(LoweringError::Other(format!("found &T in input where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
Err(())
}
},
ast::TypeName::Box(box_ty) => { self.errors.push(match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(_) => LoweringError::Other(format!("found Box<T> in input where T is an opaque, but owned opaques aren't allowed in inputs. try &T instead? T = {path}")),
_ => LoweringError::Other(format!("found Box<T> in input where T is a custom type but not opaque. non-opaques can't be behind pointers, and opaques in inputs can't be owned. T = {path}")),
}
}
_ => LoweringError::Other(format!("found Box<T> in input where T isn't a custom type. T = {box_ty}")),
});
Err(())
}
ast::TypeName::Option(opt_ty) => { match opt_ty.as_ref() {
ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref()
{
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => match path
.resolve(in_path, self.env)
{
ast::CustomType::Opaque(opaque) => { let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability); let lifetimes = ltl.lower_generics(
&path.lifetimes,
&opaque.lifetimes,
ref_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(Type::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
borrow,
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<&T> in input where T is a custom type, but it's not opaque. T = {ref_ty}")));
Err(())
}
},
_ => { self.errors.push(LoweringError::Other(format!("found Option<&T> in input, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
Err(())
}
},
ast::TypeName::Box(box_ty) => { // we could see whats in the box here too self.errors.push(LoweringError::Other(format!("found Option<Box<T>> in input, but box isn't allowed in inputs. T = {box_ty}")));
Err(())
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<T> in input, where T isn't a reference but Option<T> in inputs requires that T is a reference to an opaque. T = {opt_ty}")));
Err(())
}
}
}
ast::TypeName::Result(_, _, _) => { self.errors.push(LoweringError::Other( "Results can only appear as the top-level return type of methods".into(),
));
Err(())
}
ast::TypeName::Writeable => { self.errors.push(LoweringError::Other( "Writeables can only appear as the last parameter of a method".into(),
));
Err(())
}
ast::TypeName::StrReference(lifetime, encoding) => Ok(Type::Slice(Slice::Str(
lifetime.as_ref().map(|lt| ltl.lower_lifetime(lt)),
*encoding,
))),
ast::TypeName::StrSlice(encoding) => Ok(Type::Slice(Slice::Strs(*encoding))),
ast::TypeName::PrimitiveSlice(lm, prim) => Ok(Type::Slice(Slice::Primitive(
lm.as_ref()
.map(|(lt, m)| Borrow::new(ltl.lower_lifetime(lt), *m)),
PrimitiveType::from_ast(*prim),
))),
ast::TypeName::Unit => { self.errors.push(LoweringError::Other("Unit types can only appear as the return value of a method, or as the Ok/Err variants of a returned result".into()));
Err(())
}
}
}
/// Lowers an [`ast::TypeName`]s into an [`hir::OutType`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_out_type(
&mutself,
ty: &ast::TypeName,
ltl: &mutimpl LifetimeLowerer,
in_path: &ast::Path,
in_struct: bool,
) -> Result<OutType, ()> { match ty {
ast::TypeName::Primitive(prim) => {
Ok(OutType::Primitive(PrimitiveType::from_ast(*prim)))
}
ast::TypeName::Ordering => { if in_struct { self.errors.push(LoweringError::Other( "Found cmp::Ordering in struct field, it is only allowed in return types"
.to_string(),
));
Err(())
} else {
Ok(Type::Primitive(PrimitiveType::Int(IntType::I8)))
}
}
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Struct(strct) => { let lifetimes =
ltl.lower_generics(&path.lifetimes, &strct.lifetimes, ty.is_self());
iflet Some(tcx_id) = self.lookup_id.resolve_struct(strct) {
Ok(OutType::Struct(ReturnableStructPath::Struct(
StructPath::new(lifetimes, tcx_id),
)))
} elseiflet Some(tcx_id) = self.lookup_id.resolve_out_struct(strct) {
Ok(OutType::Struct(ReturnableStructPath::OutStruct(
OutStructPath::new(lifetimes, tcx_id),
)))
} else {
unreachable!("struct `{}` wasn't found in the set of structs or out-structs, this is a bug.", strct.name);
}
}
ast::CustomType::Opaque(_) => { self.errors.push(LoweringError::Other(format!( "Opaque passed by value in input: {path}"
)));
Err(())
}
ast::CustomType::Enum(enm) => { let tcx_id = self.lookup_id.resolve_enum(enm).expect( "can't find enum in lookup map, which contains all enums from env",
);
Ok(OutType::Enum(EnumPath::new(tcx_id)))
}
}
}
ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(opaque) => { let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability); let lifetimes = ltl.lower_generics(
&path.lifetimes,
&opaque.lifetimes,
ref_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(false),
MaybeOwn::Borrow(borrow),
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found &T in output where T is a custom type, but not opaque. T = {ref_ty}")));
Err(())
}
}
}
_ => { self.errors.push(LoweringError::Other(format!("found &T in output where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
Err(())
}
},
ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(opaque) => { let lifetimes = ltl.lower_generics(
&path.lifetimes,
&opaque.lifetimes,
box_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(false),
MaybeOwn::Own,
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found Box<T> in output where T is a custom type but not opaque. non-opaques can't be behind pointers. T = {path}")));
Err(())
}
}
}
_ => { self.errors.push(LoweringError::Other(format!( "found Box<T> in output where T isn't a custom type. T = {box_ty}"
)));
Err(())
}
},
ast::TypeName::Option(opt_ty) => match opt_ty.as_ref() {
ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(opaque) => { let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability); let lifetimes = ltl.lower_generics(
&path.lifetimes,
&opaque.lifetimes,
ref_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
MaybeOwn::Borrow(borrow),
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<&T> where T is a custom type, but it's not opaque. T = {ref_ty}")));
Err(())
}
}
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<&T>, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
Err(())
}
},
ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => { match path.resolve(in_path, self.env) {
ast::CustomType::Opaque(opaque) => { let lifetimes = ltl.lower_generics(
&path.lifetimes,
&opaque.lifetimes,
box_ty.is_self(),
); let tcx_id = self.lookup_id.resolve_opaque(opaque).expect( "can't find opaque in lookup map, which contains all opaques from env",
);
Ok(OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
MaybeOwn::Own,
tcx_id,
)))
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<Box<T>> where T is a custom type, but it's not opaque. T = {box_ty}")));
Err(())
}
}
}
_ => { self.errors.push(LoweringError::Other(format!("found Option<Box<T>>, but T isn't a custom type and therefore not opaque. T = {box_ty}")));
Err(())
}
},
_ => { self.errors.push(LoweringError::Other(format!("found Option<T>, where T isn't a reference but Option<T> requires that T is a reference to an opaque. T = {opt_ty}")));
Err(())
}
},
ast::TypeName::Result(_, _, _) => { self.errors.push(LoweringError::Other( "Results can only appear as the top-level return type of methods".into(),
));
Err(())
}
ast::TypeName::Writeable => { self.errors.push(LoweringError::Other( "Writeables can only appear as the last parameter of a method".into(),
));
Err(())
}
ast::TypeName::StrReference(lifetime, encoding) => Ok(OutType::Slice(Slice::Str(
lifetime.as_ref().map(|l| ltl.lower_lifetime(l)),
*encoding,
))),
ast::TypeName::StrSlice(..) => { self.errors.push(LoweringError::Other( "String slices can only be an input type".into(),
));
Err(())
}
ast::TypeName::PrimitiveSlice(lm, prim) => Ok(OutType::Slice(Slice::Primitive(
lm.as_ref()
.map(|(lt, m)| Borrow::new(ltl.lower_lifetime(lt), *m)),
PrimitiveType::from_ast(*prim),
))),
ast::TypeName::Unit => { self.errors.push(LoweringError::Other("Unit types can only appear as the return value of a method, or as the Ok/Err variants of a returned result".into()));
Err(())
}
}
}
/// Lowers an [`ast::SelfParam`] into an [`hir::ParamSelf`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_self_param(
&mutself,
self_param: &ast::SelfParam,
self_param_ltl: SelfParamLifetimeLowerer<'ast>,
method_full_path: &ast::Ident, // for better error msg
in_path: &ast::Path,
) -> Result<(ParamSelf, ParamLifetimeLowerer<'ast>), ()> { match self_param.path_type.resolve(in_path, self.env) {
ast::CustomType::Struct(strct) => { iflet Some(tcx_id) = self.lookup_id.resolve_struct(strct) { if self_param.reference.is_some() { self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes a reference to a struct as a self parameter, which isn't allowed")));
Err(())
} else { letmut param_ltl = self_param_ltl.no_self_ref();
// Even if we explicitly write out the type of `self` like // `self: Foo<'a>`, the `'a` is still not considered for // elision according to rustc, so is_self=true. let type_lifetimes = param_ltl.lower_generics(
&self_param.path_type.lifetimes[..],
&strct.lifetimes, true,
);
Ok((
ParamSelf::new(SelfType::Struct(StructPath::new(
type_lifetimes,
tcx_id,
))),
param_ltl,
))
}
} elseifself.lookup_id.resolve_out_struct(strct).is_some() { iflet Some((lifetime, _)) = &self_param.reference { self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an out-struct as the self parameter, which isn't allowed. Also, it's behind a reference, `{lifetime}`, but only opaques can be behind references")));
Err(())
} else { self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an out-struct as the self parameter, which isn't allowed")));
Err(())
}
} else {
unreachable!( "struct `{}` wasn't found in the set of structs or out-structs, this is a bug.",
strct.name
);
}
}
ast::CustomType::Opaque(opaque) => { let tcx_id = self
.lookup_id
.resolve_opaque(opaque)
.expect("opaque is in env");
iflet Some((lifetime, mutability)) = &self_param.reference { let (borrow_lifetime, mut param_ltl) = self_param_ltl.lower_self_ref(lifetime); let borrow = Borrow::new(borrow_lifetime, *mutability); let lifetimes = param_ltl.lower_generics(
&self_param.path_type.lifetimes,
&opaque.lifetimes, true,
);
Ok((
ParamSelf::new(SelfType::Opaque(OpaquePath::new(
lifetimes,
NonOptional,
borrow,
tcx_id,
))),
param_ltl,
))
} else { self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs")));
Err(())
}
}
ast::CustomType::Enum(enm) => { let tcx_id = self.lookup_id.resolve_enum(enm).expect("enum is in env");
/// Lowers an [`ast::Param`] into an [`hir::Param`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. /// /// Note that this expects that if there was a writeable param at the end in /// the method, it's not passed into here. fn lower_param(
&mutself,
param: &ast::Param,
ltl: &mutimpl LifetimeLowerer,
in_path: &ast::Path,
) -> Result<Param, ()> { let name = self.lower_ident(¶m.name, "param name"); let ty = self.lower_type(¶m.ty, ltl, in_path);
Ok(Param::new(name?, ty?))
}
/// Lowers many [`ast::Param`]s into a vector of [`hir::Param`]s. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. /// /// Note that this expects that if there was a writeable param at the end in /// the method, `ast_params` was sliced to not include it. This happens in /// `self.lower_method`, the caller of this function. fn lower_many_params(
&mutself,
ast_params: &[ast::Param], mut param_ltl: ParamLifetimeLowerer<'ast>,
in_path: &ast::Path,
) -> Result<(Vec<Param>, ReturnLifetimeLowerer<'ast>), ()> { letmut params = Ok(Vec::with_capacity(ast_params.len()));
for param in ast_params { let param = self.lower_param(param, &mut param_ltl, in_path);
/// Lowers the return type of an [`ast::Method`] into a [`hir::ReturnFallability`]. /// /// If there are any errors, they're pushed to `errors` and `None` is returned. fn lower_return_type(
&mutself,
return_type: Option<&ast::TypeName>,
takes_writeable: bool, mut return_ltl: ReturnLifetimeLowerer<'_>,
in_path: &ast::Path,
) -> Result<(ReturnType, LifetimeEnv), ()> { let writeable_or_unit = if takes_writeable {
SuccessType::Writeable
} else {
SuccessType::Unit
}; match return_type.unwrap_or(&ast::TypeName::Unit) {
ast::TypeName::Result(ok_ty, err_ty, _) => { let ok_ty = match ok_ty.as_ref() {
ast::TypeName::Unit => Ok(writeable_or_unit),
ty => self
.lower_out_type(ty, &mut return_ltl, in_path, false)
.map(SuccessType::OutType),
}; let err_ty = match err_ty.as_ref() {
ast::TypeName::Unit => Ok(None),
ty => self
.lower_out_type(ty, &mut return_ltl, in_path, false)
.map(Some),
};
/// Lowers a lifetime env found on a type /// /// Should not be extended to return LifetimeEnv<Method>, which needs to use the lifetime /// lowerers to handle elision. fn lower_type_lifetime_env(&mutself, ast: &ast::LifetimeEnv) -> Result<LifetimeEnv, ()> { let nodes = ast
.nodes
.iter()
.map(|lt| self.lower_named_lifetime(lt))
.collect::<Result<_, ()>>()?;
Ok(LifetimeEnv::new(nodes, ast.nodes.len()))
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.34 Sekunden
(vorverarbeitet am 2026-06-23)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.