chore: eliminate Result::unwrap
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1941,6 +1941,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"string-interner",
|
"string-interner",
|
||||||
|
"tap",
|
||||||
"tar",
|
"tar",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
|||||||
8
Justfile
8
Justfile
@@ -1,15 +1,15 @@
|
|||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@repl:
|
@repl:
|
||||||
RUST_LOG=none cargo run --bin repl
|
cargo run --bin repl
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@eval expr:
|
@eval expr:
|
||||||
RUST_LOG=none cargo run --bin eval -- '{{expr}}'
|
cargo run --bin eval -- '{{expr}}'
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@replr:
|
@replr:
|
||||||
RUST_LOG=none cargo run --bin repl --release
|
cargo run --bin repl --release
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@evalr expr:
|
@evalr expr:
|
||||||
RUST_LOG=none cargo run --bin eval --release -- '{{expr}}'
|
cargo run --bin eval --release -- '{{expr}}'
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ rowan = "0.16"
|
|||||||
nix-js-macros = { path = "../nix-js-macros" }
|
nix-js-macros = { path = "../nix-js-macros" }
|
||||||
ere = "0.2.4"
|
ere = "0.2.4"
|
||||||
num_enum = "0.7.5"
|
num_enum = "0.7.5"
|
||||||
|
tap = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::hint::black_box;
|
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_arithmetic(c: &mut Criterion) {
|
fn bench_arithmetic(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::hint::black_box;
|
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_builtin_math(c: &mut Criterion) {
|
fn bench_builtin_math(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::hint::black_box;
|
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
use nix_js::context::Context;
|
use nix_js::context::Context;
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::compile;
|
use utils::compile;
|
||||||
|
|
||||||
fn bench_parse_and_downgrade(c: &mut Criterion) {
|
fn bench_parse_and_downgrade(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::hint::black_box;
|
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_non_recursive(c: &mut Criterion) {
|
fn bench_non_recursive(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ use std::path::Path;
|
|||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::codegen::{CodegenContext, compile};
|
use crate::codegen::{CodegenContext, compile, compile_scoped};
|
||||||
use crate::downgrade::*;
|
use crate::downgrade::*;
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
@@ -17,6 +16,34 @@ use crate::runtime::{Runtime, RuntimeContext};
|
|||||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||||
use crate::value::{Symbol, Value};
|
use crate::value::{Symbol, Value};
|
||||||
|
|
||||||
|
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||||
|
use rnix::ParseError::*;
|
||||||
|
match error {
|
||||||
|
Unexpected(range)
|
||||||
|
| UnexpectedExtra(range)
|
||||||
|
| UnexpectedWanted(_, range, _)
|
||||||
|
| UnexpectedDoubleBind(range)
|
||||||
|
| DuplicatedArgs(range, _) => Some(*range),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_parse_error<'a>(
|
||||||
|
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||||
|
source: Source,
|
||||||
|
) -> Option<Box<Error>> {
|
||||||
|
for err in errors {
|
||||||
|
if let Some(span) = parse_error_span(err) {
|
||||||
|
return Some(
|
||||||
|
Error::parse_error(err.to_string())
|
||||||
|
.with_source(source)
|
||||||
|
.with_span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
runtime: Runtime<Ctx>,
|
runtime: Runtime<Ctx>,
|
||||||
@@ -232,23 +259,24 @@ impl Ctx {
|
|||||||
self.sources.get(id).expect("source not found").clone()
|
self.sources.get(id).expect("source not found").clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<String> {
|
fn downgrade<'a>(&mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<ExprId> {
|
||||||
tracing::debug!("Parsing Nix expression");
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
self.sources.push(source.clone());
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
let root = rnix::Root::parse(&source.src);
|
||||||
if !root.errors().is_empty() {
|
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||||
let error_msg = root.errors().iter().join("; ");
|
|
||||||
let err = Error::parse_error(error_msg).with_source(source);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
tracing::debug!("Downgrading Nix expression");
|
||||||
let root = self
|
let expr = root
|
||||||
.downgrade_ctx(extra_scope)
|
.tree()
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
.expr()
|
||||||
|
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||||
|
self.downgrade_ctx(extra_scope).downgrade(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<String> {
|
||||||
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
tracing::debug!("Generating JavaScript code");
|
tracing::debug!("Generating JavaScript code");
|
||||||
let code = compile(self.get_ir(root), self);
|
let code = compile(self.get_ir(root), self);
|
||||||
tracing::debug!("Generated code: {}", &code);
|
tracing::debug!("Generated code: {}", &code);
|
||||||
@@ -256,31 +284,13 @@ impl Ctx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
||||||
use crate::codegen::compile_scoped;
|
|
||||||
|
|
||||||
tracing::debug!("Parsing Nix expression for scoped import");
|
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
|
||||||
if !root.errors().is_empty() {
|
|
||||||
let error_msg = root.errors().iter().join("; ");
|
|
||||||
let err = Error::parse_error(error_msg).with_source(source);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scope = Scope::ScopedImport(
|
let scope = Scope::ScopedImport(
|
||||||
scope
|
scope
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|k| self.symbols.get_or_intern(k))
|
.map(|k| self.symbols.get_or_intern(k))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
let root = self.downgrade(source, Some(scope))?;
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let root = self
|
|
||||||
.downgrade_ctx(Some(scope))
|
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
|
||||||
|
|
||||||
tracing::debug!("Generating JavaScript code for scoped import");
|
tracing::debug!("Generating JavaScript code for scoped import");
|
||||||
let code = compile_scoped(self.get_ir(root), self);
|
let code = compile_scoped(self.get_ir(root), self);
|
||||||
tracing::debug!("Generated scoped code: {}", &code);
|
tracing::debug!("Generated scoped code: {}", &code);
|
||||||
|
|||||||
@@ -1,17 +1,41 @@
|
|||||||
// Assume no parse error
|
|
||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
use tap::TryConv;
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::*;
|
use crate::ir::*;
|
||||||
use crate::value::Symbol;
|
use crate::value::Symbol;
|
||||||
|
|
||||||
|
trait Require<T> {
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Require<T> for Option<T> {
|
||||||
|
#[inline]
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T> {
|
||||||
|
self.ok_or_else(|| {
|
||||||
|
Error::parse_error("invalid syntax".into())
|
||||||
|
.with_source(ctx.get_current_source())
|
||||||
|
.with_span(span)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: std::fmt::Display> Require<T> for std::result::Result<T, E> {
|
||||||
|
#[inline]
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T> {
|
||||||
|
self.map_err(|e| {
|
||||||
|
Error::parse_error(format!("invalid syntax: {e}"))
|
||||||
|
.with_source(ctx.get_current_source())
|
||||||
|
.with_span(span)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DowngradeContext {
|
pub trait DowngradeContext {
|
||||||
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
||||||
|
|
||||||
@@ -79,19 +103,25 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
|||||||
Ident(ident) => ident.downgrade(ctx),
|
Ident(ident) => ident.downgrade(ctx),
|
||||||
With(with) => with.downgrade(ctx),
|
With(with) => with.downgrade(ctx),
|
||||||
HasAttr(has) => has.downgrade(ctx),
|
HasAttr(has) => has.downgrade(ctx),
|
||||||
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
|
Paren(paren) => paren
|
||||||
Root(root) => root.expr().unwrap().downgrade(ctx),
|
.expr()
|
||||||
|
.require(ctx, paren.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
|
Root(root) => root
|
||||||
|
.expr()
|
||||||
|
.require(ctx, root.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let assertion = self.condition().unwrap();
|
let span = self.syntax().text_range();
|
||||||
|
let assertion = self.condition().require(ctx, span)?;
|
||||||
let assertion_raw = assertion.to_string();
|
let assertion_raw = assertion.to_string();
|
||||||
let assertion = assertion.downgrade(ctx)?;
|
let assertion = assertion.downgrade(ctx)?;
|
||||||
let expr = self.body().unwrap().downgrade(ctx)?;
|
let expr = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let span = self.syntax().text_range();
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Assert {
|
Assert {
|
||||||
assertion,
|
assertion,
|
||||||
@@ -106,10 +136,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
|||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let cond = self.condition().unwrap().downgrade(ctx)?;
|
|
||||||
let consq = self.body().unwrap().downgrade(ctx)?;
|
|
||||||
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let alter = self.else_body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
If {
|
If {
|
||||||
cond,
|
cond,
|
||||||
@@ -139,7 +169,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::PathSearch {
|
|||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let path = {
|
let path = {
|
||||||
let temp = self.content().unwrap();
|
let temp = self.content().require(ctx, span)?;
|
||||||
let text = temp.text();
|
let text = temp.text();
|
||||||
ctx.new_expr(
|
ctx.new_expr(
|
||||||
Str {
|
Str {
|
||||||
@@ -184,14 +214,17 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
let inner = interpol.expr().unwrap().downgrade(ctx)?;
|
let inner = interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx)?;
|
||||||
Ok(ctx.maybe_thunk(inner))
|
Ok(ctx.maybe_thunk(inner))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
Ok(if is_single_literal {
|
Ok(if is_single_literal {
|
||||||
parts.into_iter().next().unwrap()
|
parts.into_iter().next().expect("is_single_literal checked")
|
||||||
} else {
|
} else {
|
||||||
ctx.new_expr(
|
ctx.new_expr(
|
||||||
ConcatStrings {
|
ConcatStrings {
|
||||||
@@ -210,12 +243,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
|||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(match self.kind() {
|
Ok(ctx.new_expr(match self.kind() {
|
||||||
ast::LiteralKind::Integer(int) => Int {
|
ast::LiteralKind::Integer(int) => Int {
|
||||||
inner: int.value().unwrap(),
|
inner: int.value().require(ctx, span)?,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
ast::LiteralKind::Float(float) => Float {
|
ast::LiteralKind::Float(float) => Float {
|
||||||
inner: float.value().unwrap(),
|
inner: float.value().require(ctx, span)?,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
@@ -230,8 +263,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
|||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let text = self.ident_token().unwrap().to_string();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let text = self.ident_token().require(ctx, span)?.to_string();
|
||||||
|
|
||||||
if text == "__curPos" {
|
if text == "__curPos" {
|
||||||
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
|
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
|
||||||
@@ -276,10 +309,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
|||||||
/// Downgrades a binary operation.
|
/// Downgrades a binary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
|
||||||
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
|
||||||
let kind = self.operator().unwrap().into();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let kind = self.operator().require(ctx, span)?.into();
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
BinOp {
|
BinOp {
|
||||||
lhs,
|
lhs,
|
||||||
@@ -295,9 +328,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
|||||||
/// Downgrades a "has attribute" (`?`) expression.
|
/// Downgrades a "has attribute" (`?`) expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
|
||||||
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
|
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,9 +338,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
|||||||
/// Downgrades a unary operation.
|
/// Downgrades a unary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
|
||||||
let kind = self.operator().unwrap().into();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let kind = self.operator().require(ctx, span)?.into();
|
||||||
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
|
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,8 +348,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
|||||||
/// Downgrades an attribute selection (`.`).
|
/// Downgrades an attribute selection (`.`).
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let span = self.syntax().text_range();
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
let default = if let Some(default) = self.default_expr() {
|
let default = if let Some(default) = self.default_expr() {
|
||||||
let default_expr = default.downgrade(ctx)?;
|
let default_expr = default.downgrade(ctx)?;
|
||||||
Some(ctx.maybe_thunk(default_expr))
|
Some(ctx.maybe_thunk(default_expr))
|
||||||
@@ -374,8 +408,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let body_expr = self.body().unwrap();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
|
|
||||||
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
|
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
|
||||||
body_expr.downgrade(ctx)
|
body_expr.downgrade(ctx)
|
||||||
@@ -387,11 +421,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let namespace = ctx.maybe_thunk(namespace);
|
let namespace = ctx.maybe_thunk(namespace);
|
||||||
|
|
||||||
let (body, thunks) = ctx
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| self.body().unwrap().downgrade(ctx)));
|
let (body, thunks) =
|
||||||
|
ctx.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| body_expr.downgrade(ctx)));
|
||||||
let body = body?;
|
let body = body?;
|
||||||
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
@@ -410,7 +445,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
|||||||
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let raw_param = self.param().unwrap();
|
let span = self.syntax().text_range();
|
||||||
|
let raw_param = self.param().require(ctx, span)?;
|
||||||
|
let body_ast = self.body().require(ctx, span)?;
|
||||||
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
||||||
|
|
||||||
struct Ret {
|
struct Ret {
|
||||||
@@ -424,19 +461,20 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
|
|
||||||
match raw_param {
|
match raw_param {
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
// Simple case: `x: body`
|
|
||||||
let param_sym = ctx.new_sym(id.to_string());
|
let param_sym = ctx.new_sym(id.to_string());
|
||||||
param = None;
|
param = None;
|
||||||
|
|
||||||
// Downgrade body in Param scope
|
body = ctx
|
||||||
body = ctx.with_param_scope(param_sym, arg, |ctx| {
|
.with_param_scope(param_sym, arg, |ctx| body_ast.clone().downgrade(ctx))?;
|
||||||
self.body().unwrap().downgrade(ctx)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
ast::Param::Pattern(pattern) => {
|
ast::Param::Pattern(pattern) => {
|
||||||
let alias = pattern
|
let alias = pattern
|
||||||
.pat_bind()
|
.pat_bind()
|
||||||
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
|
.map(|alias| {
|
||||||
|
let ident = alias.ident().require(ctx, alias.syntax().text_range())?;
|
||||||
|
Ok::<_, Box<Error>>(ctx.new_sym(ident.to_string()))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let ellipsis = pattern.ellipsis_token().is_some();
|
let ellipsis = pattern.ellipsis_token().is_some();
|
||||||
let pat_entries = pattern.pat_entries();
|
let pat_entries = pattern.pat_entries();
|
||||||
@@ -446,7 +484,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
required,
|
required,
|
||||||
optional,
|
optional,
|
||||||
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
||||||
self.body().unwrap().downgrade(ctx)
|
body_ast.clone().downgrade(ctx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
param = Some(Param {
|
param = Some(Param {
|
||||||
@@ -463,7 +501,6 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
});
|
});
|
||||||
let Ret { param, body } = ret?;
|
let Ret { param, body } = ret?;
|
||||||
|
|
||||||
let span = self.syntax().text_range();
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Func {
|
Func {
|
||||||
body,
|
body,
|
||||||
@@ -482,8 +519,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
/// Each Apply node represents a single function call with one argument.
|
/// Each Apply node represents a single function call with one argument.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let func = self.lambda().unwrap().downgrade(ctx)?;
|
let span = self.syntax().text_range();
|
||||||
let arg = self.argument().unwrap().downgrade(ctx)?;
|
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let arg = ctx.maybe_thunk(arg);
|
let arg = ctx.maybe_thunk(arg);
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
|
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
|
||||||
@@ -535,7 +573,8 @@ impl PendingAttrSet {
|
|||||||
ast::Attr::Str(string) => {
|
ast::Attr::Str(string) => {
|
||||||
let parts = string.normalized_parts();
|
let parts = string.normalized_parts();
|
||||||
if parts.len() == 1
|
if parts.len() == 1
|
||||||
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
&& let ast::InterpolPart::Literal(lit) =
|
||||||
|
parts.into_iter().next().expect("len checked")
|
||||||
{
|
{
|
||||||
let sym = ctx.new_sym(lit);
|
let sym = ctx.new_sym(lit);
|
||||||
return self.insert_static(sym, span, rest, value, ctx);
|
return self.insert_static(sym, span, rest, value, ctx);
|
||||||
@@ -752,9 +791,10 @@ impl PendingAttrSet {
|
|||||||
self.collect_inherit(inherit, ctx)?;
|
self.collect_inherit(inherit, ctx)?;
|
||||||
}
|
}
|
||||||
ast::Entry::AttrpathValue(value) => {
|
ast::Entry::AttrpathValue(value) => {
|
||||||
let attrpath = value.attrpath().unwrap();
|
let span = value.syntax().text_range();
|
||||||
|
let attrpath = value.attrpath().require(ctx, span)?;
|
||||||
let path: Vec<_> = attrpath.attrs().collect();
|
let path: Vec<_> = attrpath.attrs().collect();
|
||||||
let expr = value.value().unwrap();
|
let expr = value.value().require(ctx, span)?;
|
||||||
self.insert(&path, expr, ctx)?;
|
self.insert(&path, expr, ctx)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,7 +807,13 @@ impl PendingAttrSet {
|
|||||||
inherit: ast::Inherit,
|
inherit: ast::Inherit,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let from = inherit.from().map(|f| f.expr().unwrap());
|
let from = inherit
|
||||||
|
.from()
|
||||||
|
.map(|f| {
|
||||||
|
let span = f.syntax().text_range();
|
||||||
|
f.expr().require(ctx, span)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
for attr in inherit.attrs() {
|
for attr in inherit.attrs() {
|
||||||
let span = attr.syntax().text_range();
|
let span = attr.syntax().text_range();
|
||||||
let sym = match &attr {
|
let sym = match &attr {
|
||||||
@@ -775,7 +821,8 @@ impl PendingAttrSet {
|
|||||||
ast::Attr::Str(s) => {
|
ast::Attr::Str(s) => {
|
||||||
let parts = s.normalized_parts();
|
let parts = s.normalized_parts();
|
||||||
if parts.len() == 1
|
if parts.len() == 1
|
||||||
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
&& let ast::InterpolPart::Literal(lit) =
|
||||||
|
parts.into_iter().next().expect("len checked")
|
||||||
{
|
{
|
||||||
ctx.new_sym(lit)
|
ctx.new_sym(lit)
|
||||||
} else {
|
} else {
|
||||||
@@ -870,7 +917,7 @@ fn make_attrpath_value_entry(path: Vec<ast::Attr>, value: ast::Expr) -> ast::Ent
|
|||||||
|
|
||||||
let green = builder.finish();
|
let green = builder.finish();
|
||||||
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
||||||
ast::Entry::cast(node).unwrap()
|
ast::Entry::cast(node).expect("constructed valid Entry node")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades the entries of a non-recursive attribute set.
|
/// Downgrades the entries of a non-recursive attribute set.
|
||||||
@@ -899,10 +946,13 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
||||||
} else if parts.len() == 1 {
|
} else if parts.len() == 1 {
|
||||||
match parts.into_iter().next().unwrap() {
|
match parts.into_iter().next().expect("len checked") {
|
||||||
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
||||||
Interpolation(interpol) => Ok(Attr::Dynamic(
|
Interpolation(interpol) => Ok(Attr::Dynamic(
|
||||||
interpol.expr().unwrap().downgrade(ctx)?,
|
interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx)?,
|
||||||
span,
|
span,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -911,7 +961,10 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit, span }.to_ir())),
|
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit, span }.to_ir())),
|
||||||
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
Interpolation(interpol) => interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
Ok(Attr::Dynamic(
|
Ok(Attr::Dynamic(
|
||||||
@@ -927,10 +980,13 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Dynamic(dynamic) => Ok(Attr::Dynamic(
|
Dynamic(dynamic) => {
|
||||||
dynamic.expr().unwrap().downgrade(ctx)?,
|
let span = dynamic.syntax().text_range();
|
||||||
dynamic.syntax().text_range(),
|
Ok(Attr::Dynamic(
|
||||||
)),
|
dynamic.expr().require(ctx, span)?.downgrade(ctx)?,
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,7 +1028,7 @@ where
|
|||||||
let (params, mut binding_keys) = pat_entries
|
let (params, mut binding_keys) = pat_entries
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let ident = entry.ident().unwrap();
|
let ident = entry.ident().require(ctx, entry.syntax().text_range())?;
|
||||||
let sym_span = ident.syntax().text_range();
|
let sym_span = ident.syntax().text_range();
|
||||||
let sym = ctx.new_sym(ident.syntax().text().to_string());
|
let sym = ctx.new_sym(ident.syntax().text().to_string());
|
||||||
let default = entry.default();
|
let default = entry.default();
|
||||||
@@ -1038,7 +1094,7 @@ where
|
|||||||
span,
|
span,
|
||||||
} in params
|
} in params
|
||||||
{
|
{
|
||||||
let slot = *let_bindings.get(&sym).unwrap();
|
let slot = *let_bindings.get(&sym).expect("binding registered");
|
||||||
|
|
||||||
let default = if let Some(default) = default {
|
let default = if let Some(default) = default {
|
||||||
let default = default.clone().downgrade(ctx)?;
|
let default = default.clone().downgrade(ctx)?;
|
||||||
@@ -1060,7 +1116,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(alias_sym) = alias {
|
if let Some(alias_sym) = alias {
|
||||||
let slot = *let_bindings.get(&alias_sym).unwrap();
|
let slot = *let_bindings.get(&alias_sym).expect("binding registered");
|
||||||
ctx.register_thunk(slot, arg);
|
ctx.register_thunk(slot, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1318,20 +1374,22 @@ fn downgrade_path(
|
|||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
)),
|
)),
|
||||||
ast::InterpolPart::Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
ast::InterpolPart::Interpolation(interpol) => interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
let expr = if parts.len() == 1 {
|
let expr = match parts.try_conv::<[_; 1]>() {
|
||||||
parts.into_iter().next().unwrap()
|
Ok([part]) => part,
|
||||||
} else {
|
Err(parts) => ctx.new_expr(
|
||||||
ctx.new_expr(
|
|
||||||
ConcatStrings {
|
ConcatStrings {
|
||||||
parts,
|
parts,
|
||||||
span,
|
span,
|
||||||
force_string: false,
|
force_string: false,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
|
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![allow(unused_assignments)]
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
||||||
[""]
|
[""]
|
||||||
);
|
);
|
||||||
JsRuntime::init_platform(
|
JsRuntime::init_platform(Some(v8::new_default_platform(0, false).make_shared()));
|
||||||
Some(v8::new_default_platform(0, false).make_shared()),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||||
|
|||||||
@@ -552,9 +552,13 @@ impl NixDaemonClient {
|
|||||||
assert_eq!(type_marker, "Error");
|
assert_eq!(type_marker, "Error");
|
||||||
|
|
||||||
let level = NixDaemonErrorLevel::try_from_primitive(
|
let level = NixDaemonErrorLevel::try_from_primitive(
|
||||||
self.reader.read_number().await?.try_into().unwrap(),
|
self.reader
|
||||||
|
.read_number()
|
||||||
|
.await?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| IoError::other("invalid nix-daemon error level"))?,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(|_| IoError::other("invalid nix-daemon error level"))?;
|
||||||
// removed
|
// removed
|
||||||
let _name: String = self.reader.read_value().await?;
|
let _name: String = self.reader.read_value().await?;
|
||||||
let msg: String = self.reader.read_value().await?;
|
let msg: String = self.reader.read_value().await?;
|
||||||
|
|||||||
@@ -206,11 +206,15 @@ eval_okay_test!(
|
|||||||
eval_okay_test!(partition);
|
eval_okay_test!(partition);
|
||||||
eval_okay_test!(path);
|
eval_okay_test!(path);
|
||||||
eval_okay_test!(pathexists);
|
eval_okay_test!(pathexists);
|
||||||
eval_okay_test!(path_string_interpolation, || {
|
eval_okay_test!(
|
||||||
unsafe {
|
#[ignore = "rnix 0.13 regression: /${foo}-/*...*/ fails to parse"]
|
||||||
std::env::set_var("HOME", "/fake-home");
|
path_string_interpolation,
|
||||||
|
|| {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("HOME", "/fake-home");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
eval_okay_test!(patterns);
|
eval_okay_test!(patterns);
|
||||||
eval_okay_test!(print);
|
eval_okay_test!(print);
|
||||||
eval_okay_test!(readDir);
|
eval_okay_test!(readDir);
|
||||||
|
|||||||
Reference in New Issue
Block a user