Compare commits
7 Commits
058ef44259
...
f0812c9063
| Author | SHA1 | Date | |
|---|---|---|---|
|
f0812c9063
|
|||
|
97854afafa
|
|||
|
9545b0fcae
|
|||
|
aee46b0b49
|
|||
|
1f835e7b06
|
|||
|
9ee2dd5c08
|
|||
|
084968c08a
|
@@ -4,3 +4,7 @@ members = [
|
|||||||
"nix-js",
|
"nix-js",
|
||||||
"nix-js-macros"
|
"nix-js-macros"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.profiling]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
valgrind
|
valgrind
|
||||||
hyperfine
|
hyperfine
|
||||||
just
|
just
|
||||||
|
samply
|
||||||
|
|
||||||
nodejs
|
nodejs
|
||||||
nodePackages.npm
|
nodePackages.npm
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
let mut mut_variants = Vec::new();
|
let mut mut_variants = Vec::new();
|
||||||
let mut as_ref_arms = Vec::new();
|
let mut as_ref_arms = Vec::new();
|
||||||
let mut as_mut_arms = Vec::new();
|
let mut as_mut_arms = Vec::new();
|
||||||
|
let mut span_arms = Vec::new();
|
||||||
let mut from_impls = Vec::new();
|
let mut from_impls = Vec::new();
|
||||||
let mut to_trait_impls = Vec::new();
|
let mut to_trait_impls = Vec::new();
|
||||||
|
|
||||||
@@ -112,6 +113,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||||
from_impls.push(quote! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
@@ -140,6 +142,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||||
from_impls.push(quote! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
@@ -172,6 +175,7 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||||
|
span_arms.push(quote! { Self::#name(inner) => inner.span });
|
||||||
from_impls.push(quote! {
|
from_impls.push(quote! {
|
||||||
impl From<#inner_type> for #base_name {
|
impl From<#inner_type> for #base_name {
|
||||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||||
@@ -223,6 +227,12 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
#( #as_mut_arms ),*
|
#( #as_mut_arms ),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn span(&self) -> rnix::TextRange {
|
||||||
|
match self {
|
||||||
|
#( #span_arms ),*
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `From` implementations for converting variant structs into the main enum.
|
// `From` implementations for converting variant structs into the main enum.
|
||||||
|
|||||||
@@ -365,65 +365,52 @@ const specialAttrs = new Set([
|
|||||||
|
|
||||||
export const derivation = (args: NixValue): NixAttrs => {
|
export const derivation = (args: NixValue): NixAttrs => {
|
||||||
const attrs = forceAttrs(args);
|
const attrs = forceAttrs(args);
|
||||||
const strict = derivationStrict(args);
|
|
||||||
|
|
||||||
const outputs: string[] = extractOutputs(attrs);
|
const outputs: string[] = extractOutputs(attrs);
|
||||||
const drvName = validateName(attrs);
|
|
||||||
const collectedContext: NixStringContext = new Set();
|
|
||||||
const builder = validateBuilder(attrs, collectedContext);
|
|
||||||
const platform = validateSystem(attrs);
|
|
||||||
const structuredAttrs = "__structuredAttrs" in attrs ? force(attrs.__structuredAttrs) === true : false;
|
|
||||||
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
|
|
||||||
const drvArgs = extractArgs(attrs, collectedContext);
|
|
||||||
|
|
||||||
const baseAttrs: NixAttrs = {
|
const strictThunk = createThunk(() => derivationStrict(args), "derivationStrict");
|
||||||
|
|
||||||
|
const commonAttrs: NixAttrs = { ...attrs };
|
||||||
|
|
||||||
|
const outputToAttrListElement = (outputName: string): { name: string; value: NixAttrs } => {
|
||||||
|
const value: NixAttrs = {
|
||||||
|
...commonAttrs,
|
||||||
|
outPath: createThunk(() => (force(strictThunk) as NixAttrs)[outputName], `outPath_${outputName}`),
|
||||||
|
drvPath: createThunk(() => (force(strictThunk) as NixAttrs).drvPath, "drvPath"),
|
||||||
type: "derivation",
|
type: "derivation",
|
||||||
drvPath: strict.drvPath,
|
|
||||||
name: drvName,
|
|
||||||
builder,
|
|
||||||
system: platform,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (drvArgs.length > 0) {
|
|
||||||
baseAttrs.args = drvArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!structuredAttrs) {
|
|
||||||
for (const [key, value] of Object.entries(attrs)) {
|
|
||||||
if (!specialAttrs.has(key) && !outputs.includes(key)) {
|
|
||||||
const forcedValue = force(value);
|
|
||||||
if (!(ignoreNulls && forcedValue === null)) {
|
|
||||||
baseAttrs[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputsList: NixAttrs[] = [];
|
|
||||||
|
|
||||||
for (const outputName of outputs) {
|
|
||||||
const outputObj: NixAttrs = {
|
|
||||||
...baseAttrs,
|
|
||||||
outPath: strict[outputName],
|
|
||||||
outputName,
|
outputName,
|
||||||
};
|
};
|
||||||
outputsList.push(outputObj);
|
return { name: outputName, value };
|
||||||
}
|
};
|
||||||
|
|
||||||
baseAttrs.drvAttrs = attrs;
|
const outputsList = outputs.map(outputToAttrListElement);
|
||||||
for (const [i, outputName] of outputs.entries()) {
|
|
||||||
baseAttrs[outputName] = createThunk(() => outputsList[i], `output_${outputName}`);
|
|
||||||
}
|
|
||||||
baseAttrs.all = createThunk(() => outputsList, "all_outputs");
|
|
||||||
|
|
||||||
for (const outputObj of outputsList) {
|
for (const { name: outputName, value } of outputsList) {
|
||||||
|
commonAttrs[outputName] = createThunk(
|
||||||
|
() => outputsList.find((o) => o.name === outputName)!.value,
|
||||||
|
`output_${outputName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
commonAttrs.all = createThunk(
|
||||||
|
() => outputsList.map((o) => o.value),
|
||||||
|
"all_outputs",
|
||||||
|
);
|
||||||
|
commonAttrs.drvAttrs = attrs;
|
||||||
|
|
||||||
|
for (const { value: outputObj } of outputsList) {
|
||||||
|
for (const { name: outputName } of outputsList) {
|
||||||
|
outputObj[outputName] = createThunk(
|
||||||
|
() => outputsList.find((o) => o.name === outputName)!.value,
|
||||||
|
`output_${outputName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
outputObj.all = createThunk(
|
||||||
|
() => outputsList.map((o) => o.value),
|
||||||
|
"all_outputs",
|
||||||
|
);
|
||||||
outputObj.drvAttrs = attrs;
|
outputObj.drvAttrs = attrs;
|
||||||
for (const [i, outputName] of outputs.entries()) {
|
|
||||||
outputObj[outputName] = createThunk(() => outputsList[i], `output_${outputName}`);
|
|
||||||
}
|
|
||||||
outputObj.all = createThunk(() => outputsList, "all_outputs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputsList[0];
|
return outputsList[0].value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -141,10 +141,9 @@ export const all =
|
|||||||
export const any =
|
export const any =
|
||||||
(pred: NixValue) =>
|
(pred: NixValue) =>
|
||||||
(list: NixValue): boolean => {
|
(list: NixValue): boolean => {
|
||||||
const forcedList = forceList(list);
|
// CppNix forces `pred` eagerly
|
||||||
if (forcedList.length) {
|
|
||||||
const f = forceFunction(pred);
|
const f = forceFunction(pred);
|
||||||
|
const forcedList = forceList(list);
|
||||||
|
// `false` when no element
|
||||||
return forcedList.some((e) => forceBool(f(e)));
|
return forcedList.some((e) => forceBool(f(e)));
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* All functionality is exported via the global `Nix` object
|
* All functionality is exported via the global `Nix` object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createThunk, force, isThunk, IS_THUNK, DEBUG_THUNKS } from "./thunk";
|
import { createThunk, force, isThunk, IS_THUNK, DEBUG_THUNKS, forceDeepSafe, IS_CYCLE } from "./thunk";
|
||||||
import {
|
import {
|
||||||
select,
|
select,
|
||||||
selectWithDefault,
|
selectWithDefault,
|
||||||
@@ -34,9 +34,11 @@ export type NixRuntime = typeof Nix;
|
|||||||
export const Nix = {
|
export const Nix = {
|
||||||
createThunk,
|
createThunk,
|
||||||
force,
|
force,
|
||||||
|
forceDeepSafe,
|
||||||
forceBool,
|
forceBool,
|
||||||
isThunk,
|
isThunk,
|
||||||
IS_THUNK,
|
IS_THUNK,
|
||||||
|
IS_CYCLE,
|
||||||
HAS_CONTEXT,
|
HAS_CONTEXT,
|
||||||
IS_PATH,
|
IS_PATH,
|
||||||
DEBUG_THUNKS,
|
DEBUG_THUNKS,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
|
import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
|
||||||
|
import { HAS_CONTEXT } from "./string-context";
|
||||||
|
import { IS_PATH } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symbol used to mark objects as thunks
|
* Symbol used to mark objects as thunks
|
||||||
@@ -132,3 +134,50 @@ export const force = (value: NixValue): NixStrictValue => {
|
|||||||
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => {
|
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => {
|
||||||
return new NixThunk(func, label);
|
return new NixThunk(func, label);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symbol to mark cyclic references detected during deep forcing
|
||||||
|
*/
|
||||||
|
export const IS_CYCLE = Symbol("is_cycle");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker object for cyclic references
|
||||||
|
*/
|
||||||
|
export const CYCLE_MARKER = { [IS_CYCLE]: true };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deeply force a value, handling cycles by returning a special marker.
|
||||||
|
* Uses WeakSet to track seen objects and avoid infinite recursion.
|
||||||
|
* Returns a fully forced value where thunks are replaced with their results.
|
||||||
|
* Cyclic references are replaced with CYCLE_MARKER.
|
||||||
|
*/
|
||||||
|
export const forceDeepSafe = (value: NixValue, seen: WeakSet<object> = new WeakSet()): NixStrictValue => {
|
||||||
|
const forced = force(value);
|
||||||
|
|
||||||
|
if (forced === null || typeof forced !== "object") {
|
||||||
|
return forced;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen.has(forced)) {
|
||||||
|
return CYCLE_MARKER;
|
||||||
|
}
|
||||||
|
seen.add(forced);
|
||||||
|
|
||||||
|
if (HAS_CONTEXT in forced || IS_PATH in forced) {
|
||||||
|
return forced;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(forced)) {
|
||||||
|
return forced.map((item) => forceDeepSafe(item, seen));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof forced === "object") {
|
||||||
|
const result: Record<string, NixValue> = {};
|
||||||
|
for (const [key, val] of Object.entries(forced)) {
|
||||||
|
result[key] = forceDeepSafe(val, seen);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return forced;
|
||||||
|
};
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
|||||||
Impl => {
|
Impl => {
|
||||||
code!(
|
code!(
|
||||||
buf, ctx;
|
buf, ctx;
|
||||||
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
|
"Nix.withContext(\"while evaluating the -> operator\"," self.span ",()=>(!Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PipeL => {
|
PipeL => {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl Context {
|
|||||||
|
|
||||||
tracing::debug!("Executing JavaScript");
|
tracing::debug!("Executing JavaScript");
|
||||||
self.runtime
|
self.runtime
|
||||||
.eval(format!("Nix.force({code})"), &mut self.ctx)
|
.eval(format!("Nix.forceDeepSafe({code})"), &mut self.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_code(&mut self, source: Source) -> Result<String> {
|
pub fn compile_code(&mut self, source: Source) -> Result<String> {
|
||||||
@@ -274,7 +274,7 @@ impl<'a, 'ctx> ScopeGuard<'a, 'ctx> {
|
|||||||
|
|
||||||
pub struct DowngradeCtx<'ctx> {
|
pub struct DowngradeCtx<'ctx> {
|
||||||
ctx: &'ctx mut Ctx,
|
ctx: &'ctx mut Ctx,
|
||||||
irs: Vec<Option<Ir>>,
|
irs: Vec<Ir>,
|
||||||
scopes: Vec<Scope<'ctx>>,
|
scopes: Vec<Scope<'ctx>>,
|
||||||
arg_id: usize,
|
arg_id: usize,
|
||||||
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
thunk_scopes: Vec<Vec<(ExprId, ExprId)>>,
|
||||||
@@ -294,18 +294,18 @@ impl<'ctx> DowngradeCtx<'ctx> {
|
|||||||
|
|
||||||
impl DowngradeContext for DowngradeCtx<'_> {
|
impl DowngradeContext for DowngradeCtx<'_> {
|
||||||
fn new_expr(&mut self, expr: Ir) -> ExprId {
|
fn new_expr(&mut self, expr: Ir) -> ExprId {
|
||||||
self.irs.push(Some(expr));
|
self.irs.push(expr);
|
||||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
fn new_arg(&mut self, span: TextRange) -> ExprId {
|
||||||
self.irs.push(Some(
|
self.irs.push(
|
||||||
Arg {
|
Arg {
|
||||||
inner: ArgId(self.arg_id),
|
inner: ArgId(self.arg_id),
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
));
|
);
|
||||||
self.arg_id += 1;
|
self.arg_id += 1;
|
||||||
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
ExprId(self.ctx.irs.len() + self.irs.len() - 1)
|
||||||
}
|
}
|
||||||
@@ -317,8 +317,6 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
self.irs
|
self.irs
|
||||||
.get(id.0 - self.ctx.irs.len())
|
.get(id.0 - self.ctx.irs.len())
|
||||||
.expect("ExprId out of bounds")
|
.expect("ExprId out of bounds")
|
||||||
.as_ref()
|
|
||||||
.expect("maybe_thunk called on an extracted expr")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,22 +402,9 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_ir(&mut self, id: ExprId) -> Ir {
|
|
||||||
let local_id = id.0 - self.ctx.irs.len();
|
|
||||||
self.irs
|
|
||||||
.get_mut(local_id)
|
|
||||||
.expect("ExprId out of bounds")
|
|
||||||
.take()
|
|
||||||
.expect("extract_expr called on an already extracted expr")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
fn replace_ir(&mut self, id: ExprId, expr: Ir) {
|
||||||
let local_id = id.0 - self.ctx.irs.len();
|
let local_id = id.0 - self.ctx.irs.len();
|
||||||
let _ = self
|
*self.irs.get_mut(local_id).expect("ExprId out of bounds") = expr;
|
||||||
.irs
|
|
||||||
.get_mut(local_id)
|
|
||||||
.expect("ExprId out of bounds")
|
|
||||||
.insert(expr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source {
|
fn get_current_source(&self) -> Source {
|
||||||
@@ -429,8 +414,11 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
||||||
let start = self.ctx.irs.len() + self.irs.len();
|
let start = self.ctx.irs.len() + self.irs.len();
|
||||||
self.irs.extend(std::iter::repeat_with(|| None).take(slots));
|
let range = (start..start + slots).map(ExprId);
|
||||||
(start..start + slots).map(ExprId)
|
let span = synthetic_span();
|
||||||
|
// Fill reserved slots with placeholder value
|
||||||
|
self.irs.extend(range.clone().map(|slot| Thunk { inner: slot, span }.to_ir()));
|
||||||
|
range
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||||
@@ -441,7 +429,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
|
let top_level = self.new_expr(TopLevel { body, thunks, span }.to_ir());
|
||||||
self.ctx
|
self.ctx
|
||||||
.irs
|
.irs
|
||||||
.extend(self.irs.into_iter().map(Option::unwrap));
|
.extend(self.irs);
|
||||||
Ok(top_level)
|
Ok(top_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
nix-js/src/ir.rs
133
nix-js/src/ir.rs
@@ -3,8 +3,7 @@ use hashbrown::HashMap;
|
|||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Result, Source};
|
||||||
use crate::value::format_symbol;
|
|
||||||
use nix_js_macros::ir;
|
use nix_js_macros::ir;
|
||||||
|
|
||||||
mod downgrade;
|
mod downgrade;
|
||||||
@@ -28,7 +27,6 @@ pub trait DowngradeContext {
|
|||||||
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
|
||||||
|
|
||||||
fn get_ir(&self, id: ExprId) -> &Ir;
|
fn get_ir(&self, id: ExprId) -> &Ir;
|
||||||
fn extract_ir(&mut self, id: ExprId) -> Ir;
|
|
||||||
fn replace_ir(&mut self, id: ExprId, expr: Ir);
|
fn replace_ir(&mut self, id: ExprId, expr: Ir);
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
|
||||||
fn get_current_source(&self) -> Source;
|
fn get_current_source(&self) -> Source;
|
||||||
@@ -77,134 +75,6 @@ ir! {
|
|||||||
CurPos,
|
CurPos,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ir {
|
|
||||||
pub fn span(&self) -> TextRange {
|
|
||||||
match self {
|
|
||||||
Ir::Int(i) => i.span,
|
|
||||||
Ir::Float(f) => f.span,
|
|
||||||
Ir::Bool(b) => b.span,
|
|
||||||
Ir::Null(n) => n.span,
|
|
||||||
Ir::Str(s) => s.span,
|
|
||||||
Ir::AttrSet(a) => a.span,
|
|
||||||
Ir::List(l) => l.span,
|
|
||||||
Ir::HasAttr(h) => h.span,
|
|
||||||
Ir::BinOp(b) => b.span,
|
|
||||||
Ir::UnOp(u) => u.span,
|
|
||||||
Ir::Select(s) => s.span,
|
|
||||||
Ir::If(i) => i.span,
|
|
||||||
Ir::Call(c) => c.span,
|
|
||||||
Ir::Assert(a) => a.span,
|
|
||||||
Ir::ConcatStrings(c) => c.span,
|
|
||||||
Ir::Path(p) => p.span,
|
|
||||||
Ir::Func(f) => f.span,
|
|
||||||
Ir::TopLevel(t) => t.span,
|
|
||||||
Ir::Arg(a) => a.span,
|
|
||||||
Ir::Thunk(e) => e.span,
|
|
||||||
Ir::Builtins(b) => b.span,
|
|
||||||
Ir::Builtin(b) => b.span,
|
|
||||||
Ir::CurPos(c) => c.span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttrSet {
|
|
||||||
fn _insert(
|
|
||||||
&mut self,
|
|
||||||
mut path: impl Iterator<Item = Attr>,
|
|
||||||
name: Attr,
|
|
||||||
value: ExprId,
|
|
||||||
ctx: &mut impl DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
if let Some(attr) = path.next() {
|
|
||||||
// If the path is not yet exhausted, we need to recurse deeper.
|
|
||||||
match attr {
|
|
||||||
Attr::Str(ident, span) => {
|
|
||||||
// If the next attribute is a static string.
|
|
||||||
if let Some(&(id, _)) = self.stcs.get(&ident) {
|
|
||||||
// If a sub-attrset already exists, recurse into it.
|
|
||||||
let mut ir = ctx.extract_ir(id);
|
|
||||||
let result = ir
|
|
||||||
.as_mut()
|
|
||||||
.try_unwrap_attr_set()
|
|
||||||
.map_err(|_| {
|
|
||||||
// This path segment exists but is not an attrset, which is an error.
|
|
||||||
Error::downgrade_error(format!(
|
|
||||||
"attribute '{}' already defined but is not an attribute set",
|
|
||||||
format_symbol(ctx.get_sym(ident)),
|
|
||||||
),
|
|
||||||
ctx.get_current_source(),
|
|
||||||
span
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.and_then(|attrs| attrs._insert(path, name, value, ctx));
|
|
||||||
ctx.replace_ir(id, ir);
|
|
||||||
result?;
|
|
||||||
} else {
|
|
||||||
// Create a new sub-attrset because this path doesn't exist yet.
|
|
||||||
// FIXME: span
|
|
||||||
let mut attrs = AttrSet {
|
|
||||||
stcs: Default::default(),
|
|
||||||
dyns: Default::default(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
attrs._insert(path, name, value, ctx)?;
|
|
||||||
let attrs_expr = ctx.new_expr(attrs.to_ir());
|
|
||||||
self.stcs.insert(ident, (attrs_expr, span));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Attr::Dynamic(dynamic, span) => {
|
|
||||||
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
|
||||||
// We cannot merge with existing dynamic attributes at this stage.
|
|
||||||
// FIXME: span
|
|
||||||
let mut attrs = AttrSet {
|
|
||||||
stcs: Default::default(),
|
|
||||||
dyns: Default::default(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
attrs._insert(path, name, value, ctx)?;
|
|
||||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir()), span));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is the final attribute in the path, so insert the value here.
|
|
||||||
match name {
|
|
||||||
Attr::Str(ident, span) => {
|
|
||||||
if self.stcs.insert(ident, (value, span)).is_some() {
|
|
||||||
return Err(Error::downgrade_error(
|
|
||||||
format!(
|
|
||||||
"attribute '{}' already defined",
|
|
||||||
format_symbol(ctx.get_sym(ident)),
|
|
||||||
),
|
|
||||||
ctx.get_current_source(),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Attr::Dynamic(dynamic, span) => {
|
|
||||||
self.dyns.push((dynamic, value, span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(
|
|
||||||
&mut self,
|
|
||||||
path: Vec<Attr>,
|
|
||||||
value: ExprId,
|
|
||||||
ctx: &mut impl DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut path = path.into_iter();
|
|
||||||
// The last part of the path is the name of the attribute to be inserted.
|
|
||||||
let name = path
|
|
||||||
.next_back()
|
|
||||||
.expect("empty attrpath passed. this is a bug");
|
|
||||||
self._insert(path, name, value, ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ExprId(pub usize);
|
pub struct ExprId(pub usize);
|
||||||
@@ -216,6 +86,7 @@ pub type SymId = SymbolU32;
|
|||||||
pub struct ArgId(pub usize);
|
pub struct ArgId(pub usize);
|
||||||
|
|
||||||
/// Represents a key in an attribute path.
|
/// Represents a key in an attribute path.
|
||||||
|
#[allow(unused)]
|
||||||
#[derive(Debug, TryUnwrap)]
|
#[derive(Debug, TryUnwrap)]
|
||||||
pub enum Attr {
|
pub enum Attr {
|
||||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||||
|
|||||||
@@ -220,21 +220,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
|||||||
|
|
||||||
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
|
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
|
||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
|
downgrade_rec_bindings(entries, ctx, span)
|
||||||
// Create plain attrset as body with inherit
|
|
||||||
let mut attrs = AttrSet {
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
for sym in binding_keys {
|
|
||||||
let expr = ctx.lookup(*sym, synthetic_span())?;
|
|
||||||
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ctx.new_expr(attrs.to_ir()))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,10 +307,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
|||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||||
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 bindings = downgrade_static_attrs(self, ctx)?;
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let binding_keys: Vec<_> = bindings.keys().copied().collect();
|
let attrset_expr = downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
|
||||||
|
// Create plain attrset as body with inherit
|
||||||
let attrset_expr = ctx.with_let_scope(bindings, |ctx| {
|
|
||||||
let mut attrs = AttrSet {
|
let mut attrs = AttrSet {
|
||||||
stcs: HashMap::new(),
|
stcs: HashMap::new(),
|
||||||
dyns: Vec::new(),
|
dyns: Vec::new(),
|
||||||
@@ -332,12 +317,11 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for sym in binding_keys {
|
for sym in binding_keys {
|
||||||
// FIXME: span
|
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||||
let expr = ctx.lookup(sym, synthetic_span())?;
|
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
||||||
attrs.stcs.insert(sym, (expr, synthetic_span()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result::Ok(ctx.new_expr(attrs.to_ir()))
|
Ok(ctx.new_expr(attrs.to_ir()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let body_sym = ctx.new_sym("body".to_string());
|
let body_sym = ctx.new_sym("body".to_string());
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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;
|
use rnix::ast::{self, HasEntry};
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -14,75 +14,303 @@ use crate::value::format_symbol;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Downgrades the entries of an attribute set.
|
enum PendingValue {
|
||||||
/// This handles `inherit` and `attrpath = value;` entries.
|
Expr(ast::Expr),
|
||||||
pub fn downgrade_attrs(
|
InheritFrom(ast::Expr, SymId, TextRange),
|
||||||
attrs: impl ast::HasEntry + AstNode,
|
InheritScope(SymId, TextRange),
|
||||||
ctx: &mut impl DowngradeContext,
|
Set(PendingAttrSet),
|
||||||
) -> Result<AttrSet> {
|
ExtendedRecAttrSet {
|
||||||
let entries = attrs.entries();
|
base: ast::AttrSet,
|
||||||
let mut attrs = AttrSet {
|
extensions: Vec<ast::Entry>,
|
||||||
stcs: HashMap::new(),
|
span: TextRange,
|
||||||
dyns: Vec::new(),
|
},
|
||||||
span: attrs.syntax().text_range(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
match entry {
|
|
||||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
|
||||||
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(attrs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades attribute set entries for a `let...in` expression.
|
struct PendingAttrSet {
|
||||||
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
|
stcs: HashMap<SymId, (PendingValue, TextRange)>,
|
||||||
/// as `let` bindings must be statically known.
|
dyns: Vec<(ast::Attr, PendingValue, TextRange)>,
|
||||||
pub fn downgrade_static_attrs(
|
span: TextRange,
|
||||||
attrs: impl ast::HasEntry + AstNode,
|
|
||||||
ctx: &mut impl DowngradeContext,
|
|
||||||
) -> Result<HashMap<SymId, ExprId>> {
|
|
||||||
let entries = attrs.entries();
|
|
||||||
let mut attrs = AttrSet {
|
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
span: attrs.syntax().text_range(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
match entry {
|
|
||||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
|
||||||
ast::Entry::AttrpathValue(value) => {
|
|
||||||
downgrade_static_attrpathvalue(value, &mut attrs, ctx)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(attrs.stcs.into_iter().map(|(k, (v, _))| (k, v)).collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades an `inherit` statement.
|
impl PendingAttrSet {
|
||||||
/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`.
|
fn new(span: TextRange) -> Self {
|
||||||
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
Self {
|
||||||
pub fn downgrade_inherit(
|
stcs: HashMap::new(),
|
||||||
inherit: ast::Inherit,
|
dyns: Vec::new(),
|
||||||
stcs: &mut HashMap<SymId, (ExprId, rnix::TextRange)>,
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
path: &[ast::Attr],
|
||||||
|
value: ast::Expr,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Downgrade the `from` expression if it exists.
|
let first = path.first().expect("empty attrpath passed. this is a bug");
|
||||||
let from = if let Some(from) = inherit.from() {
|
let rest = &path[1..];
|
||||||
Some(from.expr().unwrap().downgrade(ctx)?)
|
let span = first.syntax().text_range();
|
||||||
|
|
||||||
|
match first {
|
||||||
|
ast::Attr::Ident(ident) => {
|
||||||
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
|
self.insert_static(sym, span, rest, value, ctx)
|
||||||
|
}
|
||||||
|
ast::Attr::Str(string) => {
|
||||||
|
let parts = string.normalized_parts();
|
||||||
|
if parts.len() == 1
|
||||||
|
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
||||||
|
{
|
||||||
|
let sym = ctx.new_sym(lit);
|
||||||
|
return self.insert_static(sym, span, rest, value, ctx);
|
||||||
|
}
|
||||||
|
self.insert_dynamic(first.clone(), span, rest, value)
|
||||||
|
}
|
||||||
|
ast::Attr::Dynamic(_) => self.insert_dynamic(first.clone(), span, rest, value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_static(
|
||||||
|
&mut self,
|
||||||
|
sym: SymId,
|
||||||
|
span: TextRange,
|
||||||
|
path: &[ast::Attr],
|
||||||
|
value: ast::Expr,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !path.is_empty() {
|
||||||
|
match self.stcs.entry(sym) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let (existing, existing_span) = entry.get_mut();
|
||||||
|
|
||||||
|
if let PendingValue::Expr(ast::Expr::AttrSet(attrset)) = existing
|
||||||
|
&& attrset.rec_token().is_some()
|
||||||
|
{
|
||||||
|
let base = attrset.clone();
|
||||||
|
let base_span = attrset.syntax().text_range();
|
||||||
|
let ext_entry = make_attrpath_value_entry(path.to_vec(), value);
|
||||||
|
*existing = PendingValue::ExtendedRecAttrSet {
|
||||||
|
base,
|
||||||
|
extensions: vec![ext_entry],
|
||||||
|
span: base_span,
|
||||||
|
};
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let PendingValue::ExtendedRecAttrSet { extensions, .. } = existing {
|
||||||
|
let ext_entry = make_attrpath_value_entry(path.to_vec(), value);
|
||||||
|
extensions.push(ext_entry);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let nested = Self::ensure_pending_set(existing, ctx, *existing_span)?;
|
||||||
|
nested.insert(path, value, ctx)
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
let mut nested = PendingAttrSet::new(span);
|
||||||
|
nested.insert(path, value, ctx)?;
|
||||||
|
entry.insert((PendingValue::Set(nested), span));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
match self.stcs.entry(sym) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let (existing, existing_span) = entry.get_mut();
|
||||||
|
Self::merge_value(existing, *existing_span, value, span, ctx)
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert((PendingValue::Expr(value), span));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_dynamic(
|
||||||
|
&mut self,
|
||||||
|
attr: ast::Attr,
|
||||||
|
span: TextRange,
|
||||||
|
path: &[ast::Attr],
|
||||||
|
value: ast::Expr,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !path.is_empty() {
|
||||||
|
let mut nested = PendingAttrSet::new(span);
|
||||||
|
nested.insert_dynamic(
|
||||||
|
path[0].clone(),
|
||||||
|
path[0].syntax().text_range(),
|
||||||
|
&path[1..],
|
||||||
|
value,
|
||||||
|
)?;
|
||||||
|
self.dyns.push((attr, PendingValue::Set(nested), span));
|
||||||
|
} else {
|
||||||
|
self.dyns.push((attr, PendingValue::Expr(value), span));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_pending_set<'a>(
|
||||||
|
value: &'a mut PendingValue,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
span: TextRange,
|
||||||
|
) -> Result<&'a mut PendingAttrSet> {
|
||||||
|
match value {
|
||||||
|
PendingValue::Set(set) => Ok(set),
|
||||||
|
PendingValue::Expr(expr) => {
|
||||||
|
if let ast::Expr::AttrSet(attrset) = expr {
|
||||||
|
let mut nested = PendingAttrSet::new(attrset.syntax().text_range());
|
||||||
|
nested.collect_entries(attrset.entries(), ctx)?;
|
||||||
|
*value = PendingValue::Set(nested);
|
||||||
|
match value {
|
||||||
|
PendingValue::Set(set) => Ok(set),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined but is not an attribute set".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PendingValue::ExtendedRecAttrSet { .. } => Err(Error::downgrade_error(
|
||||||
|
"cannot add nested attributes to rec attribute set with extensions".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
span,
|
||||||
|
)),
|
||||||
|
PendingValue::InheritFrom(..) | PendingValue::InheritScope(..) => {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined (inherited)".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_value(
|
||||||
|
existing: &mut PendingValue,
|
||||||
|
existing_span: TextRange,
|
||||||
|
new_value: ast::Expr,
|
||||||
|
new_span: TextRange,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
match existing {
|
||||||
|
PendingValue::Set(existing_set) => {
|
||||||
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
||||||
|
existing_set.collect_entries(new_attrset.entries(), ctx)
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined as attribute set".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
new_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PendingValue::Expr(existing_expr) => {
|
||||||
|
if let ast::Expr::AttrSet(existing_attrset) = existing_expr {
|
||||||
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
||||||
|
let is_rec = existing_attrset.rec_token().is_some();
|
||||||
|
if is_rec {
|
||||||
|
let base = existing_attrset.clone();
|
||||||
|
let extensions: Vec<_> = new_attrset.entries().collect();
|
||||||
|
*existing = PendingValue::ExtendedRecAttrSet {
|
||||||
|
base,
|
||||||
|
extensions,
|
||||||
|
span: existing_attrset.syntax().text_range(),
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let mut merged =
|
||||||
|
PendingAttrSet::new(existing_attrset.syntax().text_range());
|
||||||
|
merged.collect_entries(existing_attrset.entries(), ctx)?;
|
||||||
|
merged.collect_entries(new_attrset.entries(), ctx)?;
|
||||||
|
*existing = PendingValue::Set(merged);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined as attribute set".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
new_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
existing_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PendingValue::ExtendedRecAttrSet { extensions, .. } => {
|
||||||
|
if let ast::Expr::AttrSet(new_attrset) = new_value {
|
||||||
|
extensions.extend(new_attrset.entries());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined as attribute set".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
new_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PendingValue::InheritFrom(..) | PendingValue::InheritScope(..) => {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"attribute already defined (inherited)".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
existing_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_entries(
|
||||||
|
&mut self,
|
||||||
|
entries: impl Iterator<Item = ast::Entry>,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
for entry in entries {
|
||||||
|
match entry {
|
||||||
|
ast::Entry::Inherit(inherit) => {
|
||||||
|
self.collect_inherit(inherit, ctx)?;
|
||||||
|
}
|
||||||
|
ast::Entry::AttrpathValue(value) => {
|
||||||
|
let attrpath = value.attrpath().unwrap();
|
||||||
|
let path: Vec<_> = attrpath.attrs().collect();
|
||||||
|
let expr = value.value().unwrap();
|
||||||
|
self.insert(&path, expr, ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_inherit(
|
||||||
|
&mut self,
|
||||||
|
inherit: ast::Inherit,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let from = inherit.from().map(|f| f.expr().unwrap());
|
||||||
for attr in inherit.attrs() {
|
for attr in inherit.attrs() {
|
||||||
let span = attr.syntax().text_range();
|
let span = attr.syntax().text_range();
|
||||||
let ident = match downgrade_attr(attr, ctx)? {
|
let sym = match &attr {
|
||||||
Attr::Str(ident, _) => ident,
|
ast::Attr::Ident(ident) => ctx.new_sym(ident.to_string()),
|
||||||
_ => {
|
ast::Attr::Str(s) => {
|
||||||
// `inherit` does not allow dynamic attributes.
|
let parts = s.normalized_parts();
|
||||||
|
if parts.len() == 1
|
||||||
|
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
||||||
|
{
|
||||||
|
ctx.new_sym(lit)
|
||||||
|
} else {
|
||||||
|
return Err(Error::downgrade_error(
|
||||||
|
"dynamic attributes not allowed in inherit".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ast::Attr::Dynamic(_) => {
|
||||||
return Err(Error::downgrade_error(
|
return Err(Error::downgrade_error(
|
||||||
"dynamic attributes not allowed in inherit".to_string(),
|
"dynamic attributes not allowed in inherit".to_string(),
|
||||||
ctx.get_current_source(),
|
ctx.get_current_source(),
|
||||||
@@ -90,41 +318,100 @@ pub fn downgrade_inherit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let expr = if let Some(expr) = from {
|
|
||||||
let select_expr = ctx.new_expr(
|
if self.stcs.contains_key(&sym) {
|
||||||
Select {
|
|
||||||
expr,
|
|
||||||
attrpath: vec![Attr::Str(ident, span)],
|
|
||||||
default: None,
|
|
||||||
span,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
);
|
|
||||||
ctx.maybe_thunk(select_expr)
|
|
||||||
} else {
|
|
||||||
ctx.lookup(ident, span)?
|
|
||||||
};
|
|
||||||
match stcs.entry(ident) {
|
|
||||||
Entry::Occupied(occupied) => {
|
|
||||||
return Err(Error::downgrade_error(
|
return Err(Error::downgrade_error(
|
||||||
format!(
|
format!(
|
||||||
"attribute '{}' already defined",
|
"attribute '{}' already defined",
|
||||||
format_symbol(ctx.get_sym(*occupied.key()))
|
format_symbol(ctx.get_sym(sym))
|
||||||
),
|
),
|
||||||
ctx.get_current_source(),
|
ctx.get_current_source(),
|
||||||
span,
|
span,
|
||||||
)
|
));
|
||||||
.with_span(span)
|
|
||||||
.with_source(ctx.get_current_source()));
|
|
||||||
}
|
}
|
||||||
Entry::Vacant(vacant) => vacant.insert((expr, span)),
|
|
||||||
|
let pending_value = if let Some(ref from_expr) = from {
|
||||||
|
PendingValue::InheritFrom(from_expr.clone(), sym, span)
|
||||||
|
} else {
|
||||||
|
PendingValue::InheritScope(sym, span)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.stcs.insert(sym, (pending_value, span));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_attrpath_value_entry(path: Vec<ast::Attr>, value: ast::Expr) -> ast::Entry {
|
||||||
|
use rnix::{NixLanguage, SyntaxKind};
|
||||||
|
use rowan::{GreenNodeBuilder, Language, NodeOrToken};
|
||||||
|
|
||||||
|
let mut builder = GreenNodeBuilder::new();
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(SyntaxKind::NODE_ATTRPATH_VALUE));
|
||||||
|
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(SyntaxKind::NODE_ATTRPATH));
|
||||||
|
for attr in path {
|
||||||
|
fn add_node(builder: &mut GreenNodeBuilder, node: &rowan::SyntaxNode<NixLanguage>) {
|
||||||
|
for child in node.children_with_tokens() {
|
||||||
|
match child {
|
||||||
|
NodeOrToken::Node(n) => {
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(n.kind()));
|
||||||
|
add_node(builder, &n);
|
||||||
|
builder.finish_node();
|
||||||
|
}
|
||||||
|
NodeOrToken::Token(t) => {
|
||||||
|
builder.token(NixLanguage::kind_to_raw(t.kind()), t.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(attr.syntax().kind()));
|
||||||
|
add_node(&mut builder, attr.syntax());
|
||||||
|
builder.finish_node();
|
||||||
|
}
|
||||||
|
builder.finish_node();
|
||||||
|
|
||||||
|
builder.token(NixLanguage::kind_to_raw(SyntaxKind::TOKEN_ASSIGN), "=");
|
||||||
|
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(value.syntax().kind()));
|
||||||
|
fn add_node_value(builder: &mut GreenNodeBuilder, node: &rowan::SyntaxNode<NixLanguage>) {
|
||||||
|
for child in node.children_with_tokens() {
|
||||||
|
match child {
|
||||||
|
NodeOrToken::Node(n) => {
|
||||||
|
builder.start_node(NixLanguage::kind_to_raw(n.kind()));
|
||||||
|
add_node_value(builder, &n);
|
||||||
|
builder.finish_node();
|
||||||
|
}
|
||||||
|
NodeOrToken::Token(t) => {
|
||||||
|
builder.token(NixLanguage::kind_to_raw(t.kind()), t.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_node_value(&mut builder, value.syntax());
|
||||||
|
builder.finish_node();
|
||||||
|
|
||||||
|
builder.token(NixLanguage::kind_to_raw(SyntaxKind::TOKEN_SEMICOLON), ";");
|
||||||
|
|
||||||
|
builder.finish_node();
|
||||||
|
|
||||||
|
let green = builder.finish();
|
||||||
|
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
||||||
|
ast::Entry::cast(node).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downgrades the entries of a non-recursive attribute set.
|
||||||
|
pub fn downgrade_attrs(
|
||||||
|
attrs: impl ast::HasEntry + AstNode,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<AttrSet> {
|
||||||
|
let span = attrs.syntax().text_range();
|
||||||
|
let mut pending = PendingAttrSet::new(span);
|
||||||
|
pending.collect_entries(attrs.entries(), ctx)?;
|
||||||
|
finalize_pending_set::<_, true>(pending, &HashMap::new(), ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades a single attribute key (part of an attribute path).
|
/// Downgrades a single attribute key (part of an attribute path).
|
||||||
/// An attribute can be a static identifier, an interpolated string, or a dynamic expression.
|
|
||||||
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||||
use ast::Attr::*;
|
use ast::Attr::*;
|
||||||
use ast::InterpolPart::*;
|
use ast::InterpolPart::*;
|
||||||
@@ -139,7 +426,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
|||||||
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 {
|
||||||
// If the string has only one part, it's either a literal or a single interpolation.
|
|
||||||
match parts.into_iter().next().unwrap() {
|
match parts.into_iter().next().unwrap() {
|
||||||
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(
|
||||||
@@ -148,7 +434,6 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the string has multiple parts, it's an interpolated string that must be concatenated.
|
|
||||||
let parts = parts
|
let parts = parts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
@@ -180,40 +465,6 @@ pub fn downgrade_attrpath(
|
|||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades an `attrpath = value;` expression and inserts it into an `AttrSet`.
|
|
||||||
pub fn downgrade_attrpathvalue(
|
|
||||||
value: ast::AttrpathValue,
|
|
||||||
attrs: &mut AttrSet,
|
|
||||||
ctx: &mut impl DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
|
||||||
let value = value.value().unwrap().downgrade(ctx)?;
|
|
||||||
let value = ctx.maybe_thunk(value);
|
|
||||||
attrs.insert(path, value, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A stricter version of `downgrade_attrpathvalue` for `let...in` bindings.
|
|
||||||
/// It ensures that the attribute path contains no dynamic parts.
|
|
||||||
pub fn downgrade_static_attrpathvalue(
|
|
||||||
value: ast::AttrpathValue,
|
|
||||||
attrs: &mut AttrSet,
|
|
||||||
ctx: &mut impl DowngradeContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let attrpath_node = value.attrpath().unwrap();
|
|
||||||
let path = downgrade_attrpath(attrpath_node.clone(), ctx)?;
|
|
||||||
if let Some(&Attr::Dynamic(_, span)) =
|
|
||||||
path.iter().find(|attr| matches!(attr, Attr::Dynamic(..)))
|
|
||||||
{
|
|
||||||
return Err(Error::downgrade_error(
|
|
||||||
"dynamic attributes not allowed in let bindings".to_string(),
|
|
||||||
ctx.get_current_source(),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let value = value.value().unwrap().downgrade(ctx)?;
|
|
||||||
attrs.insert(path, value, ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PatternBindings {
|
pub struct PatternBindings {
|
||||||
pub body: ExprId,
|
pub body: ExprId,
|
||||||
pub required: Vec<SymId>,
|
pub required: Vec<SymId>,
|
||||||
@@ -221,7 +472,6 @@ pub struct PatternBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for Lambda pattern parameters.
|
/// Helper function for Lambda pattern parameters.
|
||||||
/// Processes pattern entries like `{ a, b ? 2, ... }@alias` and creates bindings.
|
|
||||||
pub fn downgrade_pattern_bindings<Ctx>(
|
pub fn downgrade_pattern_bindings<Ctx>(
|
||||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||||
alias: Option<SymId>,
|
alias: Option<SymId>,
|
||||||
@@ -337,67 +587,67 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to downgrade entries with let bindings semantics.
|
/// Downgrades a `let...in` expression. This is a special case of rec attrs
|
||||||
/// This extracts common logic for both `rec` attribute sets and `let...in` expressions.
|
/// that disallows dynamic attributes and has a body expression.
|
||||||
pub fn downgrade_let_bindings<Ctx, F>(
|
pub fn downgrade_let_bindings<Ctx, F>(
|
||||||
entries: Vec<ast::Entry>,
|
entries: Vec<ast::Entry>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
_span: TextRange,
|
span: TextRange,
|
||||||
body_fn: F,
|
body_fn: F,
|
||||||
) -> Result<ExprId>
|
) -> Result<ExprId>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext,
|
Ctx: DowngradeContext,
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
F: FnOnce(&mut Ctx, &[SymId]) -> Result<ExprId>,
|
||||||
{
|
{
|
||||||
let mut binding_syms = HashSet::new();
|
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, span, |ctx, binding_keys, _dyns| {
|
||||||
|
body_fn(ctx, binding_keys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for entry in &entries {
|
/// Downgrades a `rec` attribute set.
|
||||||
match entry {
|
pub fn downgrade_rec_bindings<Ctx>(
|
||||||
ast::Entry::Inherit(inherit) => {
|
entries: Vec<ast::Entry>,
|
||||||
for attr in inherit.attrs() {
|
ctx: &mut Ctx,
|
||||||
if let ast::Attr::Ident(ident) = attr {
|
span: TextRange,
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
) -> Result<ExprId>
|
||||||
if !binding_syms.insert(sym) {
|
where
|
||||||
return Err(Error::downgrade_error(
|
Ctx: DowngradeContext,
|
||||||
format!(
|
{
|
||||||
"attribute '{}' already defined",
|
downgrade_rec_attrs_impl::<_, _, true>(entries, ctx, span, |ctx, binding_keys, dyns| {
|
||||||
format_symbol(ctx.get_sym(sym))
|
let mut attrs = AttrSet {
|
||||||
),
|
stcs: HashMap::new(),
|
||||||
ctx.get_current_source(),
|
dyns: dyns.to_vec(),
|
||||||
ident.syntax().text_range(),
|
span,
|
||||||
));
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::Entry::AttrpathValue(value) => {
|
|
||||||
let attrpath = value.attrpath().unwrap();
|
|
||||||
let attrs_vec: Vec<_> = attrpath.attrs().collect();
|
|
||||||
|
|
||||||
if attrs_vec.len() == 1 {
|
for sym in binding_keys {
|
||||||
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
||||||
if !binding_syms.insert(sym) {
|
|
||||||
return Err(Error::downgrade_error(
|
|
||||||
format!(
|
|
||||||
"attribute '{}' already defined",
|
|
||||||
format_symbol(ctx.get_sym(sym))
|
|
||||||
),
|
|
||||||
ctx.get_current_source(),
|
|
||||||
ident.syntax().text_range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if attrs_vec.len() > 1
|
|
||||||
&& let Some(ast::Attr::Ident(ident)) = attrs_vec.first()
|
|
||||||
{
|
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
|
||||||
binding_syms.insert(sym);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(ctx.new_expr(attrs.to_ir()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core implementation for recursive bindings (rec attrs and let-in).
|
||||||
|
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||||
|
fn downgrade_rec_attrs_impl<Ctx, F, const ALLOW_DYN: bool>(
|
||||||
|
entries: Vec<ast::Entry>,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
span: TextRange,
|
||||||
|
body_fn: F,
|
||||||
|
) -> Result<ExprId>
|
||||||
|
where
|
||||||
|
Ctx: DowngradeContext,
|
||||||
|
F: FnOnce(&mut Ctx, &[SymId], &[(ExprId, ExprId, TextRange)]) -> Result<ExprId>,
|
||||||
|
{
|
||||||
|
let mut pending = PendingAttrSet::new(span);
|
||||||
|
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
||||||
|
|
||||||
|
let binding_syms = collect_binding_syms::<_, ALLOW_DYN>(&pending, ctx)?;
|
||||||
|
|
||||||
|
let inherit_lookups = collect_inherit_lookups(&entries, ctx)?;
|
||||||
|
|
||||||
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
let binding_keys: Vec<_> = binding_syms.into_iter().collect();
|
||||||
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
let slots: Vec<_> = ctx.reserve_slots(binding_keys.len()).collect();
|
||||||
let let_bindings: HashMap<_, _> = binding_keys
|
let let_bindings: HashMap<_, _> = binding_keys
|
||||||
@@ -407,30 +657,22 @@ where
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for &slot in &slots {
|
for &slot in &slots {
|
||||||
let span = synthetic_span();
|
let slot_span = synthetic_span();
|
||||||
ctx.replace_ir(slot, Thunk { inner: slot, span }.to_ir());
|
ctx.replace_ir(
|
||||||
|
slot,
|
||||||
|
Thunk {
|
||||||
|
inner: slot,
|
||||||
|
span: slot_span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
ctx.with_let_scope(let_bindings.clone(), |ctx| {
|
||||||
let mut temp_attrs = AttrSet {
|
let finalized = finalize_pending_set::<_, ALLOW_DYN>(pending, &inherit_lookups, ctx)?;
|
||||||
stcs: HashMap::new(),
|
|
||||||
dyns: Vec::new(),
|
|
||||||
span: synthetic_span(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
match entry {
|
|
||||||
ast::Entry::Inherit(inherit) => {
|
|
||||||
downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?;
|
|
||||||
}
|
|
||||||
ast::Entry::AttrpathValue(value) => {
|
|
||||||
downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
for (sym, slot) in binding_keys.iter().copied().zip(slots.iter()) {
|
||||||
if let Some(&(expr, _)) = temp_attrs.stcs.get(&sym) {
|
if let Some(&(expr, _)) = finalized.stcs.get(&sym) {
|
||||||
ctx.register_thunk(*slot, expr);
|
ctx.register_thunk(*slot, expr);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::internal(format!(
|
return Err(Error::internal(format!(
|
||||||
@@ -440,6 +682,163 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body_fn(ctx, &binding_keys)
|
body_fn(ctx, &binding_keys, &finalized.dyns)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collects `inherit x` lookups from the outer scope before entering the rec scope.
|
||||||
|
fn collect_inherit_lookups<Ctx: DowngradeContext>(
|
||||||
|
entries: &[ast::Entry],
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<HashMap<SymId, (ExprId, TextRange)>> {
|
||||||
|
let mut inherit_lookups = HashMap::new();
|
||||||
|
for entry in entries {
|
||||||
|
if let ast::Entry::Inherit(inherit) = entry
|
||||||
|
&& inherit.from().is_none()
|
||||||
|
{
|
||||||
|
for attr in inherit.attrs() {
|
||||||
|
if let ast::Attr::Ident(ident) = attr {
|
||||||
|
let attr_span = ident.syntax().text_range();
|
||||||
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
|
let expr = ctx.lookup(sym, attr_span)?;
|
||||||
|
inherit_lookups.insert(sym, (expr, attr_span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(inherit_lookups)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects binding symbols from a pending set, checking for duplicates.
|
||||||
|
fn collect_binding_syms<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||||
|
pending: &PendingAttrSet,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<HashSet<SymId>> {
|
||||||
|
let mut binding_syms = HashSet::new();
|
||||||
|
|
||||||
|
for (sym, (_, span)) in &pending.stcs {
|
||||||
|
if !binding_syms.insert(*sym) {
|
||||||
|
return Err(Error::downgrade_error(
|
||||||
|
format!(
|
||||||
|
"attribute '{}' already defined",
|
||||||
|
format_symbol(ctx.get_sym(*sym))
|
||||||
|
),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
*span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(binding_syms)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified finalize function for PendingAttrSet.
|
||||||
|
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||||
|
fn finalize_pending_set<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||||
|
pending: PendingAttrSet,
|
||||||
|
inherit_lookups: &HashMap<SymId, (ExprId, TextRange)>,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<AttrSet> {
|
||||||
|
let mut stcs = HashMap::new();
|
||||||
|
let mut dyns = Vec::new();
|
||||||
|
|
||||||
|
for (sym, (value, value_span)) in pending.stcs {
|
||||||
|
let expr_id = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
||||||
|
stcs.insert(sym, (expr_id, value_span));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ALLOW_DYN {
|
||||||
|
for (attr, value, value_span) in pending.dyns {
|
||||||
|
let key_id = downgrade_attr(attr, ctx)?;
|
||||||
|
let key_expr = match key_id {
|
||||||
|
Attr::Dynamic(id, _) => id,
|
||||||
|
Attr::Str(sym, attr_span) => ctx.new_expr(
|
||||||
|
Str {
|
||||||
|
val: ctx.get_sym(sym).to_string(),
|
||||||
|
span: attr_span,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let value_id = finalize_pending_value::<_, ALLOW_DYN>(value, inherit_lookups, ctx)?;
|
||||||
|
dyns.push((key_expr, value_id, value_span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AttrSet {
|
||||||
|
stcs,
|
||||||
|
dyns,
|
||||||
|
span: pending.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unified finalize function for PendingValue.
|
||||||
|
/// ALLOW_DYN controls whether dynamic attributes are allowed.
|
||||||
|
fn finalize_pending_value<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
||||||
|
value: PendingValue,
|
||||||
|
inherit_lookups: &HashMap<SymId, (ExprId, TextRange)>,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<ExprId> {
|
||||||
|
match value {
|
||||||
|
PendingValue::Expr(expr) => {
|
||||||
|
if !ALLOW_DYN {
|
||||||
|
check_no_dynamic_attrs(&expr, ctx)?;
|
||||||
|
}
|
||||||
|
let id = Downgrade::downgrade(expr, ctx)?;
|
||||||
|
Ok(ctx.maybe_thunk(id))
|
||||||
|
}
|
||||||
|
PendingValue::InheritFrom(from_expr, sym, span) => {
|
||||||
|
let from_id = Downgrade::downgrade(from_expr, ctx)?;
|
||||||
|
let select = Select {
|
||||||
|
expr: from_id,
|
||||||
|
attrpath: vec![Attr::Str(sym, span)],
|
||||||
|
default: None,
|
||||||
|
span,
|
||||||
|
};
|
||||||
|
let select_id = ctx.new_expr(select.to_ir());
|
||||||
|
Ok(ctx.maybe_thunk(select_id))
|
||||||
|
}
|
||||||
|
PendingValue::InheritScope(sym, span) => {
|
||||||
|
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
||||||
|
Ok(ctx.maybe_thunk(expr))
|
||||||
|
} else {
|
||||||
|
let lookup_id = ctx.lookup(sym, span)?;
|
||||||
|
Ok(ctx.maybe_thunk(lookup_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PendingValue::Set(set) => {
|
||||||
|
let attrset = finalize_pending_set::<_, ALLOW_DYN>(set, inherit_lookups, ctx)?;
|
||||||
|
Ok(ctx.new_expr(attrset.to_ir()))
|
||||||
|
}
|
||||||
|
PendingValue::ExtendedRecAttrSet {
|
||||||
|
base,
|
||||||
|
extensions,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let mut all_entries: Vec<_> = base.entries().collect();
|
||||||
|
all_entries.extend(extensions);
|
||||||
|
downgrade_rec_bindings(all_entries, ctx, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that an expression doesn't contain dynamic attributes (for let bindings).
|
||||||
|
fn check_no_dynamic_attrs(expr: &ast::Expr, ctx: &impl DowngradeContext) -> Result<()> {
|
||||||
|
let ast::Expr::AttrSet(attrset) = expr else {
|
||||||
|
return Ok(())
|
||||||
|
};
|
||||||
|
for v in attrset.attrpath_values() {
|
||||||
|
v.attrpath().unwrap().attrs().try_for_each(|attr| {
|
||||||
|
if let ast::Attr::Dynamic(dyn_attr) = attr {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
"dynamic attributes not allowed in let bindings".to_string(),
|
||||||
|
ctx.get_current_source(),
|
||||||
|
dyn_attr.syntax().text_range(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
|||||||
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
primop_metadata_symbol: v8::Global<v8::Symbol>,
|
||||||
has_context_symbol: v8::Global<v8::Symbol>,
|
has_context_symbol: v8::Global<v8::Symbol>,
|
||||||
is_path_symbol: v8::Global<v8::Symbol>,
|
is_path_symbol: v8::Global<v8::Symbol>,
|
||||||
|
is_cycle_symbol: v8::Global<v8::Symbol>,
|
||||||
_marker: PhantomData<Ctx>,
|
_marker: PhantomData<Ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +572,13 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let (is_thunk_symbol, primop_metadata_symbol, has_context_symbol, is_path_symbol) = {
|
let (
|
||||||
|
is_thunk_symbol,
|
||||||
|
primop_metadata_symbol,
|
||||||
|
has_context_symbol,
|
||||||
|
is_path_symbol,
|
||||||
|
is_cycle_symbol,
|
||||||
|
) = {
|
||||||
deno_core::scope!(scope, &mut js_runtime);
|
deno_core::scope!(scope, &mut js_runtime);
|
||||||
Self::get_symbols(scope)?
|
Self::get_symbols(scope)?
|
||||||
};
|
};
|
||||||
@@ -582,6 +589,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
primop_metadata_symbol,
|
primop_metadata_symbol,
|
||||||
has_context_symbol,
|
has_context_symbol,
|
||||||
is_path_symbol,
|
is_path_symbol,
|
||||||
|
is_cycle_symbol,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -609,6 +617,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
|
let primop_metadata_symbol = v8::Local::new(scope, &self.primop_metadata_symbol);
|
||||||
let has_context_symbol = v8::Local::new(scope, &self.has_context_symbol);
|
let has_context_symbol = v8::Local::new(scope, &self.has_context_symbol);
|
||||||
let is_path_symbol = v8::Local::new(scope, &self.is_path_symbol);
|
let is_path_symbol = v8::Local::new(scope, &self.is_path_symbol);
|
||||||
|
let is_cycle_symbol = v8::Local::new(scope, &self.is_cycle_symbol);
|
||||||
|
|
||||||
Ok(to_value(
|
Ok(to_value(
|
||||||
local_value,
|
local_value,
|
||||||
@@ -617,10 +626,11 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
primop_metadata_symbol,
|
primop_metadata_symbol,
|
||||||
has_context_symbol,
|
has_context_symbol,
|
||||||
is_path_symbol,
|
is_path_symbol,
|
||||||
|
is_cycle_symbol,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get (IS_THUNK, PRIMOP_METADATA, HAS_CONTEXT, IS_PATH)
|
/// get (IS_THUNK, PRIMOP_METADATA, HAS_CONTEXT, IS_PATH, IS_CYCLE)
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn get_symbols(
|
fn get_symbols(
|
||||||
scope: &ScopeRef,
|
scope: &ScopeRef,
|
||||||
@@ -629,6 +639,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
v8::Global<v8::Symbol>,
|
v8::Global<v8::Symbol>,
|
||||||
v8::Global<v8::Symbol>,
|
v8::Global<v8::Symbol>,
|
||||||
v8::Global<v8::Symbol>,
|
v8::Global<v8::Symbol>,
|
||||||
|
v8::Global<v8::Symbol>,
|
||||||
)> {
|
)> {
|
||||||
let global = scope.get_current_context().global(scope);
|
let global = scope.get_current_context().global(scope);
|
||||||
let nix_key = v8::String::new(scope, "Nix")
|
let nix_key = v8::String::new(scope, "Nix")
|
||||||
@@ -659,8 +670,9 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
let primop_metadata = get_symbol("PRIMOP_METADATA")?;
|
let primop_metadata = get_symbol("PRIMOP_METADATA")?;
|
||||||
let has_context = get_symbol("HAS_CONTEXT")?;
|
let has_context = get_symbol("HAS_CONTEXT")?;
|
||||||
let is_path = get_symbol("IS_PATH")?;
|
let is_path = get_symbol("IS_PATH")?;
|
||||||
|
let is_cycle = get_symbol("IS_CYCLE")?;
|
||||||
|
|
||||||
Ok((is_thunk, primop_metadata, has_context, is_path))
|
Ok((is_thunk, primop_metadata, has_context, is_path, is_cycle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,6 +683,7 @@ fn to_value<'a>(
|
|||||||
primop_metadata_symbol: LocalSymbol<'a>,
|
primop_metadata_symbol: LocalSymbol<'a>,
|
||||||
has_context_symbol: LocalSymbol<'a>,
|
has_context_symbol: LocalSymbol<'a>,
|
||||||
is_path_symbol: LocalSymbol<'a>,
|
is_path_symbol: LocalSymbol<'a>,
|
||||||
|
is_cycle_symbol: LocalSymbol<'a>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match () {
|
match () {
|
||||||
_ if val.is_big_int() => {
|
_ if val.is_big_int() => {
|
||||||
@@ -707,6 +720,7 @@ fn to_value<'a>(
|
|||||||
primop_metadata_symbol,
|
primop_metadata_symbol,
|
||||||
has_context_symbol,
|
has_context_symbol,
|
||||||
is_path_symbol,
|
is_path_symbol,
|
||||||
|
is_cycle_symbol,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -724,6 +738,10 @@ fn to_value<'a>(
|
|||||||
return Value::Thunk;
|
return Value::Thunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_cycle(val, scope, is_cycle_symbol) {
|
||||||
|
return Value::Repeated;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path_val) = extract_path(val, scope, is_path_symbol) {
|
if let Some(path_val) = extract_path(val, scope, is_path_symbol) {
|
||||||
return Value::Path(path_val);
|
return Value::Path(path_val);
|
||||||
}
|
}
|
||||||
@@ -753,6 +771,7 @@ fn to_value<'a>(
|
|||||||
primop_metadata_symbol,
|
primop_metadata_symbol,
|
||||||
has_context_symbol,
|
has_context_symbol,
|
||||||
is_path_symbol,
|
is_path_symbol,
|
||||||
|
is_cycle_symbol,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -772,6 +791,15 @@ fn is_thunk<'a>(val: LocalValue<'a>, scope: &ScopeRef<'a, '_>, symbol: LocalSymb
|
|||||||
matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true())
|
matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_cycle<'a>(val: LocalValue<'a>, scope: &ScopeRef<'a, '_>, symbol: LocalSymbol<'a>) -> bool {
|
||||||
|
if !val.is_object() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = val.to_object(scope).expect("infallible conversion");
|
||||||
|
matches!(obj.get(scope, symbol.into()), Some(v) if v.is_true())
|
||||||
|
}
|
||||||
|
|
||||||
fn extract_string_with_context<'a>(
|
fn extract_string_with_context<'a>(
|
||||||
val: LocalValue<'a>,
|
val: LocalValue<'a>,
|
||||||
scope: &ScopeRef<'a, '_>,
|
scope: &ScopeRef<'a, '_>,
|
||||||
|
|||||||
@@ -117,15 +117,9 @@ impl Debug for AttrSet {
|
|||||||
|
|
||||||
impl Display for AttrSet {
|
impl Display for AttrSet {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
use Value::*;
|
|
||||||
write!(f, "{{")?;
|
write!(f, "{{")?;
|
||||||
for (k, v) in self.data.iter() {
|
for (k, v) in self.data.iter() {
|
||||||
write!(f, " {k} = ")?;
|
write!(f, " {k} = {v};")?;
|
||||||
match v {
|
|
||||||
List(_) => write!(f, "[ ... ];")?,
|
|
||||||
AttrSet(_) => write!(f, "{{ ... }};")?,
|
|
||||||
v => write!(f, "{v};")?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
write!(f, " }}")
|
write!(f, " }}")
|
||||||
}
|
}
|
||||||
@@ -199,7 +193,20 @@ impl Display for Value {
|
|||||||
&Float(x) => write!(f, "{x}"),
|
&Float(x) => write!(f, "{x}"),
|
||||||
&Bool(x) => write!(f, "{x}"),
|
&Bool(x) => write!(f, "{x}"),
|
||||||
Null => write!(f, "null"),
|
Null => write!(f, "null"),
|
||||||
String(x) => write!(f, r#""{x}""#),
|
String(x) => {
|
||||||
|
write!(f, "\"")?;
|
||||||
|
for c in x.chars() {
|
||||||
|
match c {
|
||||||
|
'\\' => write!(f, "\\\\")?,
|
||||||
|
'"' => write!(f, "\\\"")?,
|
||||||
|
'\n' => write!(f, "\\n")?,
|
||||||
|
'\r' => write!(f, "\\r")?,
|
||||||
|
'\t' => write!(f, "\\t")?,
|
||||||
|
c => write!(f, "{c}")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "\"")
|
||||||
|
}
|
||||||
Path(x) => write!(f, "{x}"),
|
Path(x) => write!(f, "{x}"),
|
||||||
AttrSet(x) => write!(f, "{x}"),
|
AttrSet(x) => write!(f, "{x}"),
|
||||||
List(x) => write!(f, "{x}"),
|
List(x) => write!(f, "{x}"),
|
||||||
@@ -207,7 +214,7 @@ impl Display for Value {
|
|||||||
Func => write!(f, "«lambda»"),
|
Func => write!(f, "«lambda»"),
|
||||||
PrimOp(name) => write!(f, "«primop {name}»"),
|
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||||
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
||||||
Repeated => write!(f, "<REPEATED>"),
|
Repeated => write!(f, "«repeated»"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ mod utils;
|
|||||||
use nix_js::value::Value;
|
use nix_js::value::Value;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
|
use crate::utils::eval_result;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn arithmetic() {
|
fn arithmetic() {
|
||||||
assert_eq!(eval("1 + 1"), Value::Int(2));
|
assert_eq!(eval("1 + 1"), Value::Int(2));
|
||||||
@@ -63,3 +65,8 @@ fn nested_let() {
|
|||||||
Value::Int(3)
|
Value::Int(3)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rec_inherit_fails() {
|
||||||
|
assert!(eval_result("{ inherit x; }").is_err());
|
||||||
|
}
|
||||||
|
|||||||
@@ -470,8 +470,8 @@ fn structured_attrs_basic() {
|
|||||||
Value::AttrSet(attrs) => {
|
Value::AttrSet(attrs) => {
|
||||||
assert!(attrs.contains_key("drvPath"));
|
assert!(attrs.contains_key("drvPath"));
|
||||||
assert!(attrs.contains_key("outPath"));
|
assert!(attrs.contains_key("outPath"));
|
||||||
assert!(!attrs.contains_key("foo"));
|
assert!(attrs.contains_key("foo"));
|
||||||
assert!(!attrs.contains_key("count"));
|
assert!(attrs.contains_key("count"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
@@ -492,7 +492,7 @@ fn structured_attrs_nested() {
|
|||||||
match result {
|
match result {
|
||||||
Value::AttrSet(attrs) => {
|
Value::AttrSet(attrs) => {
|
||||||
assert!(attrs.contains_key("drvPath"));
|
assert!(attrs.contains_key("drvPath"));
|
||||||
assert!(!attrs.contains_key("data"));
|
assert!(attrs.contains_key("data"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
@@ -554,7 +554,7 @@ fn ignore_nulls_true() {
|
|||||||
match result {
|
match result {
|
||||||
Value::AttrSet(attrs) => {
|
Value::AttrSet(attrs) => {
|
||||||
assert!(attrs.contains_key("foo"));
|
assert!(attrs.contains_key("foo"));
|
||||||
assert!(!attrs.contains_key("nullValue"));
|
assert!(attrs.contains_key("nullValue"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
@@ -600,8 +600,8 @@ fn ignore_nulls_with_structured_attrs() {
|
|||||||
match result {
|
match result {
|
||||||
Value::AttrSet(attrs) => {
|
Value::AttrSet(attrs) => {
|
||||||
assert!(attrs.contains_key("drvPath"));
|
assert!(attrs.contains_key("drvPath"));
|
||||||
assert!(!attrs.contains_key("foo"));
|
assert!(attrs.contains_key("foo"));
|
||||||
assert!(!attrs.contains_key("nullValue"));
|
assert!(attrs.contains_key("nullValue"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
@@ -627,8 +627,8 @@ fn all_features_combined() {
|
|||||||
assert!(attrs.contains_key("out"));
|
assert!(attrs.contains_key("out"));
|
||||||
assert!(attrs.contains_key("dev"));
|
assert!(attrs.contains_key("dev"));
|
||||||
assert!(attrs.contains_key("outPath"));
|
assert!(attrs.contains_key("outPath"));
|
||||||
assert!(!attrs.contains_key("data"));
|
assert!(attrs.contains_key("data"));
|
||||||
assert!(!attrs.contains_key("nullValue"));
|
assert!(attrs.contains_key("nullValue"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
@@ -651,7 +651,7 @@ fn fixed_output_with_structured_attrs() {
|
|||||||
Value::AttrSet(attrs) => {
|
Value::AttrSet(attrs) => {
|
||||||
assert!(attrs.contains_key("outPath"));
|
assert!(attrs.contains_key("outPath"));
|
||||||
assert!(attrs.contains_key("drvPath"));
|
assert!(attrs.contains_key("drvPath"));
|
||||||
assert!(!attrs.contains_key("data"));
|
assert!(attrs.contains_key("data"));
|
||||||
}
|
}
|
||||||
_ => panic!("Expected AttrSet"),
|
_ => panic!("Expected AttrSet"),
|
||||||
}
|
}
|
||||||
|
|||||||
278
nix-js/tests/lang.rs
Normal file
278
nix-js/tests/lang.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use nix_js::context::Context;
|
||||||
|
use nix_js::error::Source;
|
||||||
|
use nix_js::value::Value;
|
||||||
|
|
||||||
|
fn get_lang_dir() -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/lang")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_file(name: &str) -> Result<Value, String> {
|
||||||
|
let lang_dir = get_lang_dir();
|
||||||
|
let nix_path = lang_dir.join(format!("{name}.nix"));
|
||||||
|
|
||||||
|
let expr = format!(r#"import "{}""#, nix_path.display());
|
||||||
|
|
||||||
|
let mut ctx = Context::new().map_err(|e| e.to_string())?;
|
||||||
|
let source = Source::new_eval(expr).map_err(|e| e.to_string())?;
|
||||||
|
ctx.eval_code(source).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_expected(name: &str) -> String {
|
||||||
|
let lang_dir = get_lang_dir();
|
||||||
|
let exp_path = lang_dir.join(format!("{name}.exp"));
|
||||||
|
std::fs::read_to_string(exp_path)
|
||||||
|
.expect("expected file should exist")
|
||||||
|
.trim_end()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_value(value: &Value) -> String {
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! eval_okay_test {
|
||||||
|
($(#[$attr:meta])* $name:ident$(, $pre:expr)?) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
$(($pre)();)?
|
||||||
|
let test_name = concat!("eval-okay-", stringify!($name))
|
||||||
|
.replace("_", "-")
|
||||||
|
.replace("r#", "");
|
||||||
|
let result = eval_file(&test_name);
|
||||||
|
match result {
|
||||||
|
Ok(value) => {
|
||||||
|
let actual = format_value(&value);
|
||||||
|
let expected = read_expected(&test_name);
|
||||||
|
assert_eq!(actual, expected, "Output mismatch for {}", test_name);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Test {} failed to evaluate: {}", test_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! eval_fail_test {
|
||||||
|
($name:ident) => {
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let test_name = concat!("eval-fail-", stringify!($name))
|
||||||
|
.replace("_", "-")
|
||||||
|
.replace("r#", "");
|
||||||
|
let result = eval_file(&test_name);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Test {} should have failed but succeeded with: {:?}",
|
||||||
|
test_name,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eval_okay_test!(any_all);
|
||||||
|
eval_okay_test!(arithmetic);
|
||||||
|
eval_okay_test!(attrnames);
|
||||||
|
eval_okay_test!(attrs);
|
||||||
|
eval_okay_test!(attrs2);
|
||||||
|
eval_okay_test!(attrs3);
|
||||||
|
eval_okay_test!(attrs4);
|
||||||
|
eval_okay_test!(attrs5);
|
||||||
|
eval_okay_test!(#[ignore = "__overrides is not supported"] attrs6);
|
||||||
|
eval_okay_test!(#[ignore = "requires --arg/--argstr CLI flags"] autoargs);
|
||||||
|
eval_okay_test!(backslash_newline_1);
|
||||||
|
eval_okay_test!(backslash_newline_2);
|
||||||
|
eval_okay_test!(baseNameOf);
|
||||||
|
eval_okay_test!(builtins);
|
||||||
|
eval_okay_test!(builtins_add);
|
||||||
|
eval_okay_test!(callable_attrs);
|
||||||
|
eval_okay_test!(catattrs);
|
||||||
|
eval_okay_test!(closure);
|
||||||
|
eval_okay_test!(comments);
|
||||||
|
eval_okay_test!(concat);
|
||||||
|
eval_okay_test!(concatmap);
|
||||||
|
eval_okay_test!(concatstringssep);
|
||||||
|
eval_okay_test!(context);
|
||||||
|
eval_okay_test!(context_introspection);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: convertHash"] convertHash);
|
||||||
|
eval_okay_test!(curpos);
|
||||||
|
eval_okay_test!(deepseq);
|
||||||
|
eval_okay_test!(delayed_with);
|
||||||
|
eval_okay_test!(delayed_with_inherit);
|
||||||
|
eval_okay_test!(deprecate_cursed_or);
|
||||||
|
eval_okay_test!(derivation_legacy);
|
||||||
|
eval_okay_test!(dynamic_attrs);
|
||||||
|
eval_okay_test!(dynamic_attrs_2);
|
||||||
|
eval_okay_test!(dynamic_attrs_bare);
|
||||||
|
eval_okay_test!(elem);
|
||||||
|
eval_okay_test!(empty_args);
|
||||||
|
eval_okay_test!(eq);
|
||||||
|
eval_okay_test!(eq_derivations);
|
||||||
|
eval_okay_test!(filter);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: flakeRefToString"] flake_ref_to_string);
|
||||||
|
eval_okay_test!(flatten);
|
||||||
|
eval_okay_test!(float);
|
||||||
|
eval_okay_test!(floor_ceil);
|
||||||
|
eval_okay_test!(foldlStrict);
|
||||||
|
eval_okay_test!(foldlStrict_lazy_elements);
|
||||||
|
eval_okay_test!(foldlStrict_lazy_initial_accumulator);
|
||||||
|
eval_okay_test!(fromjson);
|
||||||
|
eval_okay_test!(fromjson_escapes);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: fromTOML"] fromTOML);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: fromTOML"] fromTOML_timestamps);
|
||||||
|
eval_okay_test!(functionargs);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: hashFile"] hashfile);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: hashString"] hashstring);
|
||||||
|
eval_okay_test!(getattrpos);
|
||||||
|
eval_okay_test!(getattrpos_functionargs);
|
||||||
|
eval_okay_test!(getattrpos_undefined);
|
||||||
|
eval_okay_test!(getenv, || {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("TEST_VAR", "foo")
|
||||||
|
};
|
||||||
|
});
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: hashString"] groupBy);
|
||||||
|
eval_okay_test!(r#if);
|
||||||
|
eval_okay_test!(ind_string);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: scopedImport"] import);
|
||||||
|
eval_okay_test!(inherit_attr_pos);
|
||||||
|
eval_okay_test!(inherit_from);
|
||||||
|
eval_okay_test!(intersectAttrs);
|
||||||
|
eval_okay_test!(r#let);
|
||||||
|
eval_okay_test!(list);
|
||||||
|
eval_okay_test!(listtoattrs);
|
||||||
|
eval_okay_test!(logic);
|
||||||
|
eval_okay_test!(map);
|
||||||
|
eval_okay_test!(mapattrs);
|
||||||
|
eval_okay_test!(merge_dynamic_attrs);
|
||||||
|
eval_okay_test!(nested_with);
|
||||||
|
eval_okay_test!(new_let);
|
||||||
|
eval_okay_test!(null_dynamic_attrs);
|
||||||
|
eval_okay_test!(#[ignore = "__overrides is not supported"] overrides);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: parseFlakeRef"] parse_flake_ref);
|
||||||
|
eval_okay_test!(partition);
|
||||||
|
eval_okay_test!(path);
|
||||||
|
eval_okay_test!(pathexists);
|
||||||
|
eval_okay_test!(path_string_interpolation);
|
||||||
|
eval_okay_test!(patterns);
|
||||||
|
eval_okay_test!(print);
|
||||||
|
eval_okay_test!(readDir);
|
||||||
|
eval_okay_test!(readfile);
|
||||||
|
eval_okay_test!(readFileType);
|
||||||
|
eval_okay_test!(redefine_builtin);
|
||||||
|
eval_okay_test!(regex_match);
|
||||||
|
eval_okay_test!(regex_split);
|
||||||
|
eval_okay_test!(regression_20220122);
|
||||||
|
eval_okay_test!(regression_20220125);
|
||||||
|
eval_okay_test!(regrettable_rec_attrset_merge);
|
||||||
|
eval_okay_test!(remove);
|
||||||
|
eval_okay_test!(repeated_empty_attrs);
|
||||||
|
eval_okay_test!(repeated_empty_list);
|
||||||
|
eval_okay_test!(replacestrings);
|
||||||
|
eval_okay_test!(#[ignore = "requires -I CLI flags"] search_path);
|
||||||
|
eval_okay_test!(scope_1);
|
||||||
|
eval_okay_test!(scope_2);
|
||||||
|
eval_okay_test!(scope_3);
|
||||||
|
eval_okay_test!(scope_4);
|
||||||
|
eval_okay_test!(scope_6);
|
||||||
|
eval_okay_test!(scope_7);
|
||||||
|
eval_okay_test!(seq);
|
||||||
|
eval_okay_test!(sort);
|
||||||
|
eval_okay_test!(splitversion);
|
||||||
|
eval_okay_test!(string);
|
||||||
|
eval_okay_test!(strings_as_attrs_names);
|
||||||
|
eval_okay_test!(substring);
|
||||||
|
eval_okay_test!(substring_context);
|
||||||
|
eval_okay_test!(symlink_resolution);
|
||||||
|
eval_okay_test!(tail_call_1);
|
||||||
|
eval_okay_test!(tojson);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: toXML"] toxml);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: toXML"] toxml2);
|
||||||
|
eval_okay_test!(tryeval);
|
||||||
|
eval_okay_test!(types);
|
||||||
|
eval_okay_test!(versions);
|
||||||
|
eval_okay_test!(with);
|
||||||
|
eval_okay_test!(#[ignore = "not implemented: hashString"] zipAttrsWith);
|
||||||
|
|
||||||
|
eval_fail_test!(fail_abort);
|
||||||
|
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
|
||||||
|
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
|
||||||
|
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
|
||||||
|
eval_fail_test!(fail_addErrorContext_example);
|
||||||
|
eval_fail_test!(fail_assert);
|
||||||
|
eval_fail_test!(fail_assert_equal_attrs_names);
|
||||||
|
eval_fail_test!(fail_assert_equal_attrs_names_2);
|
||||||
|
eval_fail_test!(fail_assert_equal_derivations);
|
||||||
|
eval_fail_test!(fail_assert_equal_derivations_extra);
|
||||||
|
eval_fail_test!(fail_assert_equal_floats);
|
||||||
|
eval_fail_test!(fail_assert_equal_function_direct);
|
||||||
|
eval_fail_test!(fail_assert_equal_int_float);
|
||||||
|
eval_fail_test!(fail_assert_equal_ints);
|
||||||
|
eval_fail_test!(fail_assert_equal_list_length);
|
||||||
|
eval_fail_test!(fail_assert_equal_paths);
|
||||||
|
eval_fail_test!(fail_assert_equal_type);
|
||||||
|
eval_fail_test!(fail_assert_equal_type_nested);
|
||||||
|
eval_fail_test!(fail_assert_nested_bool);
|
||||||
|
eval_fail_test!(fail_attr_name_type);
|
||||||
|
eval_fail_test!(fail_attrset_merge_drops_later_rec);
|
||||||
|
eval_fail_test!(fail_bad_string_interpolation_1);
|
||||||
|
eval_fail_test!(fail_bad_string_interpolation_2);
|
||||||
|
eval_fail_test!(fail_bad_string_interpolation_3);
|
||||||
|
eval_fail_test!(fail_bad_string_interpolation_4);
|
||||||
|
eval_fail_test!(fail_blackhole);
|
||||||
|
eval_fail_test!(fail_call_primop);
|
||||||
|
eval_fail_test!(fail_deepseq);
|
||||||
|
eval_fail_test!(fail_derivation_name);
|
||||||
|
eval_fail_test!(fail_dup_dynamic_attrs);
|
||||||
|
eval_fail_test!(fail_duplicate_traces);
|
||||||
|
eval_fail_test!(fail_eol_1);
|
||||||
|
eval_fail_test!(fail_eol_2);
|
||||||
|
eval_fail_test!(fail_eol_3);
|
||||||
|
eval_fail_test!(fail_fetchTree_negative);
|
||||||
|
eval_fail_test!(fail_fetchurl_baseName);
|
||||||
|
eval_fail_test!(fail_fetchurl_baseName_attrs);
|
||||||
|
eval_fail_test!(fail_fetchurl_baseName_attrs_name);
|
||||||
|
eval_fail_test!(fail_flake_ref_to_string_negative_integer);
|
||||||
|
eval_fail_test!(fail_foldlStrict_strict_op_application);
|
||||||
|
eval_fail_test!(fail_fromJSON_keyWithNullByte);
|
||||||
|
eval_fail_test!(fail_fromJSON_overflowing);
|
||||||
|
eval_fail_test!(fail_fromJSON_valueWithNullByte);
|
||||||
|
eval_fail_test!(fail_fromTOML_keyWithNullByte);
|
||||||
|
eval_fail_test!(fail_fromTOML_timestamps);
|
||||||
|
eval_fail_test!(fail_fromTOML_valueWithNullByte);
|
||||||
|
eval_fail_test!(fail_hashfile_missing);
|
||||||
|
eval_fail_test!(fail_infinite_recursion_lambda);
|
||||||
|
eval_fail_test!(fail_list);
|
||||||
|
eval_fail_test!(fail_missing_arg);
|
||||||
|
eval_fail_test!(fail_mutual_recursion);
|
||||||
|
eval_fail_test!(fail_nested_list_items);
|
||||||
|
eval_fail_test!(fail_nonexist_path);
|
||||||
|
eval_fail_test!(fail_not_throws);
|
||||||
|
eval_fail_test!(fail_overflowing_add);
|
||||||
|
eval_fail_test!(fail_overflowing_div);
|
||||||
|
eval_fail_test!(fail_overflowing_mul);
|
||||||
|
eval_fail_test!(fail_overflowing_sub);
|
||||||
|
eval_fail_test!(fail_path_slash);
|
||||||
|
eval_fail_test!(fail_pipe_operators);
|
||||||
|
eval_fail_test!(fail_recursion);
|
||||||
|
eval_fail_test!(fail_remove);
|
||||||
|
eval_fail_test!(fail_scope_5);
|
||||||
|
eval_fail_test!(fail_seq);
|
||||||
|
eval_fail_test!(fail_set);
|
||||||
|
eval_fail_test!(fail_set_override);
|
||||||
|
eval_fail_test!(fail_string_nul_1);
|
||||||
|
eval_fail_test!(fail_string_nul_2);
|
||||||
|
eval_fail_test!(fail_substring);
|
||||||
|
eval_fail_test!(fail_toJSON);
|
||||||
|
eval_fail_test!(fail_toJSON_non_utf_8);
|
||||||
|
eval_fail_test!(fail_to_path);
|
||||||
|
eval_fail_test!(fail_undeclared_arg);
|
||||||
|
eval_fail_test!(fail_using_set_as_attr_name);
|
||||||
1
nix-js/tests/lang/dir1/a.nix
Normal file
1
nix-js/tests/lang/dir1/a.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"a"
|
||||||
1
nix-js/tests/lang/dir2/a.nix
Normal file
1
nix-js/tests/lang/dir2/a.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"X"
|
||||||
1
nix-js/tests/lang/dir2/b.nix
Normal file
1
nix-js/tests/lang/dir2/b.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"b"
|
||||||
1
nix-js/tests/lang/dir3/a.nix
Normal file
1
nix-js/tests/lang/dir3/a.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"X"
|
||||||
1
nix-js/tests/lang/dir3/b.nix
Normal file
1
nix-js/tests/lang/dir3/b.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"X"
|
||||||
1
nix-js/tests/lang/dir3/c.nix
Normal file
1
nix-js/tests/lang/dir3/c.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"c"
|
||||||
1
nix-js/tests/lang/dir4/a.nix
Normal file
1
nix-js/tests/lang/dir4/a.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"X"
|
||||||
1
nix-js/tests/lang/dir4/c.nix
Normal file
1
nix-js/tests/lang/dir4/c.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"X"
|
||||||
8
nix-js/tests/lang/eval-fail-abort.err.exp
Normal file
8
nix-js/tests/lang/eval-fail-abort.err.exp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'abort' builtin
|
||||||
|
at /pwd/lang/eval-fail-abort.nix:1:14:
|
||||||
|
1| if true then abort "this should fail" else 1
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: evaluation aborted with the following error message: 'this should fail'
|
||||||
1
nix-js/tests/lang/eval-fail-abort.nix
Normal file
1
nix-js/tests/lang/eval-fail-abort.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
if true then abort "this should fail" else 1
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'addDrvOutputDependencies' builtin
|
||||||
|
at /pwd/lang/eval-fail-addDrvOutputDependencies-empty-context.nix:1:1:
|
||||||
|
1| builtins.addDrvOutputDependencies ""
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: context of string '' must have exactly one element, but has 0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
builtins.addDrvOutputDependencies ""
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'addDrvOutputDependencies' builtin
|
||||||
|
at /pwd/lang/eval-fail-addDrvOutputDependencies-multi-elem-context.nix:25:1:
|
||||||
|
24| in
|
||||||
|
25| builtins.addDrvOutputDependencies combo-path
|
||||||
|
| ^
|
||||||
|
26|
|
||||||
|
|
||||||
|
error: context of string '/nix/store/pg9yqs4yd85yhdm3f4i5dyaqp5jahrsz-fail.drv/nix/store/2dxd5frb715z451vbf7s8birlf3argbk-fail-2.drv' must have exactly one element, but has 2
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
let
|
||||||
|
drv0 = derivation {
|
||||||
|
name = "fail";
|
||||||
|
builder = "/bin/false";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
outputs = [
|
||||||
|
"out"
|
||||||
|
"foo"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
drv1 = derivation {
|
||||||
|
name = "fail-2";
|
||||||
|
builder = "/bin/false";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
outputs = [
|
||||||
|
"out"
|
||||||
|
"foo"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
combo-path = "${drv0.drvPath}${drv1.drvPath}";
|
||||||
|
|
||||||
|
in
|
||||||
|
builtins.addDrvOutputDependencies combo-path
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'addDrvOutputDependencies' builtin
|
||||||
|
at /pwd/lang/eval-fail-addDrvOutputDependencies-wrong-element-kind.nix:13:1:
|
||||||
|
12| in
|
||||||
|
13| builtins.addDrvOutputDependencies drv.outPath
|
||||||
|
| ^
|
||||||
|
14|
|
||||||
|
|
||||||
|
error: `addDrvOutputDependencies` can only act on derivations, not on a derivation output such as 'out'
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
let
|
||||||
|
drv = derivation {
|
||||||
|
name = "fail";
|
||||||
|
builder = "/bin/false";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
outputs = [
|
||||||
|
"out"
|
||||||
|
"foo"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
builtins.addDrvOutputDependencies drv.outPath
|
||||||
24
nix-js/tests/lang/eval-fail-addErrorContext-example.err.exp
Normal file
24
nix-js/tests/lang/eval-fail-addErrorContext-example.err.exp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
error:
|
||||||
|
… while counting down; n = 10
|
||||||
|
|
||||||
|
… while counting down; n = 9
|
||||||
|
|
||||||
|
… while counting down; n = 8
|
||||||
|
|
||||||
|
… while counting down; n = 7
|
||||||
|
|
||||||
|
… while counting down; n = 6
|
||||||
|
|
||||||
|
… while counting down; n = 5
|
||||||
|
|
||||||
|
… while counting down; n = 4
|
||||||
|
|
||||||
|
… while counting down; n = 3
|
||||||
|
|
||||||
|
… while counting down; n = 2
|
||||||
|
|
||||||
|
… while counting down; n = 1
|
||||||
|
|
||||||
|
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
|
||||||
|
|
||||||
|
error: kaboom
|
||||||
9
nix-js/tests/lang/eval-fail-addErrorContext-example.nix
Normal file
9
nix-js/tests/lang/eval-fail-addErrorContext-example.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
let
|
||||||
|
countDown =
|
||||||
|
n:
|
||||||
|
if n == 0 then
|
||||||
|
throw "kaboom"
|
||||||
|
else
|
||||||
|
builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||||
|
in
|
||||||
|
countDown 10
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ a = true; } == { a = true; b = true; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-attrs-names-2.nix:1:1:
|
||||||
|
1| assert
|
||||||
|
| ^
|
||||||
|
2| {
|
||||||
|
|
||||||
|
error: attribute names of attribute set '{ a = true; }' differs from attribute set '{ a = true; b = true; }'
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
assert
|
||||||
|
{
|
||||||
|
a = true;
|
||||||
|
} == {
|
||||||
|
a = true;
|
||||||
|
b = true;
|
||||||
|
};
|
||||||
|
throw "unreachable"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ a = true; b = true; } == { a = true; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-attrs-names.nix:1:1:
|
||||||
|
1| assert
|
||||||
|
| ^
|
||||||
|
2| {
|
||||||
|
|
||||||
|
error: attribute names of attribute set '{ a = true; b = true; }' differs from attribute set '{ a = true; }'
|
||||||
8
nix-js/tests/lang/eval-fail-assert-equal-attrs-names.nix
Normal file
8
nix-js/tests/lang/eval-fail-assert-equal-attrs-names.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
assert
|
||||||
|
{
|
||||||
|
a = true;
|
||||||
|
b = true;
|
||||||
|
} == {
|
||||||
|
a = true;
|
||||||
|
};
|
||||||
|
throw "unreachable"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ foo = { outPath = "/nix/store/0"; type = "derivation"; }; } == { foo = { devious = true; outPath = "/nix/store/1"; type = "derivation"; }; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:1:1:
|
||||||
|
1| assert
|
||||||
|
| ^
|
||||||
|
2| {
|
||||||
|
|
||||||
|
… while comparing attribute 'foo'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:3:5:
|
||||||
|
2| {
|
||||||
|
3| foo = {
|
||||||
|
| ^
|
||||||
|
4| type = "derivation";
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:8:5:
|
||||||
|
7| } == {
|
||||||
|
8| foo = {
|
||||||
|
| ^
|
||||||
|
9| type = "derivation";
|
||||||
|
|
||||||
|
… while comparing a derivation by its 'outPath' attribute
|
||||||
|
|
||||||
|
error: string '"/nix/store/0"' is not equal to string '"/nix/store/1"'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
assert
|
||||||
|
{
|
||||||
|
foo = {
|
||||||
|
type = "derivation";
|
||||||
|
outPath = "/nix/store/0";
|
||||||
|
};
|
||||||
|
} == {
|
||||||
|
foo = {
|
||||||
|
type = "derivation";
|
||||||
|
outPath = "/nix/store/1";
|
||||||
|
devious = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
throw "unreachable"
|
||||||
26
nix-js/tests/lang/eval-fail-assert-equal-derivations.err.exp
Normal file
26
nix-js/tests/lang/eval-fail-assert-equal-derivations.err.exp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ foo = { ignored = (abort "not ignored"); outPath = "/nix/store/0"; type = "derivation"; }; } == { foo = { ignored = (abort "not ignored"); outPath = "/nix/store/1"; type = "derivation"; }; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations.nix:1:1:
|
||||||
|
1| assert
|
||||||
|
| ^
|
||||||
|
2| {
|
||||||
|
|
||||||
|
… while comparing attribute 'foo'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations.nix:3:5:
|
||||||
|
2| {
|
||||||
|
3| foo = {
|
||||||
|
| ^
|
||||||
|
4| type = "derivation";
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-derivations.nix:9:5:
|
||||||
|
8| } == {
|
||||||
|
9| foo = {
|
||||||
|
| ^
|
||||||
|
10| type = "derivation";
|
||||||
|
|
||||||
|
… while comparing a derivation by its 'outPath' attribute
|
||||||
|
|
||||||
|
error: string '"/nix/store/0"' is not equal to string '"/nix/store/1"'
|
||||||
15
nix-js/tests/lang/eval-fail-assert-equal-derivations.nix
Normal file
15
nix-js/tests/lang/eval-fail-assert-equal-derivations.nix
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
assert
|
||||||
|
{
|
||||||
|
foo = {
|
||||||
|
type = "derivation";
|
||||||
|
outPath = "/nix/store/0";
|
||||||
|
ignored = abort "not ignored";
|
||||||
|
};
|
||||||
|
} == {
|
||||||
|
foo = {
|
||||||
|
type = "derivation";
|
||||||
|
outPath = "/nix/store/1";
|
||||||
|
ignored = abort "not ignored";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
throw "unreachable"
|
||||||
22
nix-js/tests/lang/eval-fail-assert-equal-floats.err.exp
Normal file
22
nix-js/tests/lang/eval-fail-assert-equal-floats.err.exp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ b = 1; } == { b = 1.01; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:1:
|
||||||
|
1| assert { b = 1.0; } == { b = 1.01; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… while comparing attribute 'b'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:10:
|
||||||
|
1| assert { b = 1.0; } == { b = 1.01; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:26:
|
||||||
|
1| assert { b = 1.0; } == { b = 1.01; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
error: a float with value '1' is not equal to a float with value '1.01'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-floats.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-floats.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert { b = 1.0; } == { b = 1.01; };
|
||||||
|
abort "unreachable"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '((x: x) == (x: x))'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-function-direct.nix:3:1:
|
||||||
|
2| # This only compares a direct comparison and makes no claims about functions in nested structures.
|
||||||
|
3| assert (x: x) == (x: x);
|
||||||
|
| ^
|
||||||
|
4| abort "unreachable"
|
||||||
|
|
||||||
|
error: distinct functions and immediate comparisons of identical functions compare as unequal
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Note: functions in nested structures, e.g. attributes, may be optimized away by pointer identity optimization.
|
||||||
|
# This only compares a direct comparison and makes no claims about functions in nested structures.
|
||||||
|
assert (x: x) == (x: x);
|
||||||
|
abort "unreachable"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '(1 == 1.1)'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-int-float.nix:1:1:
|
||||||
|
1| assert 1 == 1.1;
|
||||||
|
| ^
|
||||||
|
2| throw "unreachable"
|
||||||
|
|
||||||
|
error: an integer with value '1' is not equal to a float with value '1.1'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-int-float.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-int-float.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert 1 == 1.1;
|
||||||
|
throw "unreachable"
|
||||||
22
nix-js/tests/lang/eval-fail-assert-equal-ints.err.exp
Normal file
22
nix-js/tests/lang/eval-fail-assert-equal-ints.err.exp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ b = 1; } == { b = 2; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:1:
|
||||||
|
1| assert { b = 1; } == { b = 2; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… while comparing attribute 'b'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:10:
|
||||||
|
1| assert { b = 1; } == { b = 2; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:24:
|
||||||
|
1| assert { b = 1; } == { b = 2; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
error: an integer with value '1' is not equal to an integer with value '2'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-ints.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-ints.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert { b = 1; } == { b = 2; };
|
||||||
|
abort "unreachable"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '([ (1) (0) ] == [ (10) ])'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-list-length.nix:1:1:
|
||||||
|
1| assert
|
||||||
|
| ^
|
||||||
|
2| [
|
||||||
|
|
||||||
|
error: list of size '2' is not equal to list of size '1', left hand side is '[ 1 0 ]', right hand side is '[ 10 ]'
|
||||||
6
nix-js/tests/lang/eval-fail-assert-equal-list-length.nix
Normal file
6
nix-js/tests/lang/eval-fail-assert-equal-list-length.nix
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
assert
|
||||||
|
[
|
||||||
|
1
|
||||||
|
0
|
||||||
|
] == [ 10 ];
|
||||||
|
throw "unreachable"
|
||||||
8
nix-js/tests/lang/eval-fail-assert-equal-paths.err.exp
Normal file
8
nix-js/tests/lang/eval-fail-assert-equal-paths.err.exp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '(/pwd/lang/foo == /pwd/lang/bar)'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-paths.nix:1:1:
|
||||||
|
1| assert ./foo == ./bar;
|
||||||
|
| ^
|
||||||
|
2| throw "unreachable"
|
||||||
|
|
||||||
|
error: path '/pwd/lang/foo' is not equal to path '/pwd/lang/bar'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-paths.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-paths.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert ./foo == ./bar;
|
||||||
|
throw "unreachable"
|
||||||
22
nix-js/tests/lang/eval-fail-assert-equal-type-nested.err.exp
Normal file
22
nix-js/tests/lang/eval-fail-assert-equal-type-nested.err.exp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ ding = false; } == { ding = null; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:1:
|
||||||
|
1| assert { ding = false; } == { ding = null; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… while comparing attribute 'ding'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:10:
|
||||||
|
1| assert { ding = false; } == { ding = null; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:31:
|
||||||
|
1| assert { ding = false; } == { ding = null; };
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
error: a Boolean of value 'false' is not equal to null of value 'null'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-type-nested.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-type-nested.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert { ding = false; } == { ding = null; };
|
||||||
|
abort "unreachable"
|
||||||
8
nix-js/tests/lang/eval-fail-assert-equal-type.err.exp
Normal file
8
nix-js/tests/lang/eval-fail-assert-equal-type.err.exp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '(false == null)'
|
||||||
|
at /pwd/lang/eval-fail-assert-equal-type.nix:1:1:
|
||||||
|
1| assert false == null;
|
||||||
|
| ^
|
||||||
|
2| abort "unreachable"
|
||||||
|
|
||||||
|
error: a Boolean of value 'false' is not equal to null of value 'null'
|
||||||
2
nix-js/tests/lang/eval-fail-assert-equal-type.nix
Normal file
2
nix-js/tests/lang/eval-fail-assert-equal-type.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert false == null;
|
||||||
|
abort "unreachable"
|
||||||
66
nix-js/tests/lang/eval-fail-assert-nested-bool.err.exp
Normal file
66
nix-js/tests/lang/eval-fail-assert-nested-bool.err.exp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the condition of the assertion '({ a = { b = [ ({ c = { d = true; }; }) ]; }; } == { a = { b = [ ({ c = { d = false; }; }) ]; }; })'
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:1:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while comparing attribute 'a'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:10:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:44:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while comparing attribute 'b'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:10:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:44:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while comparing list element 0
|
||||||
|
|
||||||
|
… while comparing attribute 'c'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:20:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:54:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while comparing attribute 'd'
|
||||||
|
|
||||||
|
… where left hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:20:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… where right hand side is
|
||||||
|
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:54:
|
||||||
|
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: boolean 'true' is not equal to boolean 'false'
|
||||||
3
nix-js/tests/lang/eval-fail-assert-nested-bool.nix
Normal file
3
nix-js/tests/lang/eval-fail-assert-nested-bool.nix
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
|
||||||
|
|
||||||
|
abort "unreachable"
|
||||||
30
nix-js/tests/lang/eval-fail-assert.err.exp
Normal file
30
nix-js/tests/lang/eval-fail-assert.err.exp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the attribute 'body'
|
||||||
|
at /pwd/lang/eval-fail-assert.nix:7:3:
|
||||||
|
6|
|
||||||
|
7| body = x "x";
|
||||||
|
| ^
|
||||||
|
8| }
|
||||||
|
|
||||||
|
… from call site
|
||||||
|
at /pwd/lang/eval-fail-assert.nix:7:10:
|
||||||
|
6|
|
||||||
|
7| body = x "x";
|
||||||
|
| ^
|
||||||
|
8| }
|
||||||
|
|
||||||
|
… while calling 'x'
|
||||||
|
at /pwd/lang/eval-fail-assert.nix:3:5:
|
||||||
|
2| x =
|
||||||
|
3| arg:
|
||||||
|
| ^
|
||||||
|
4| assert arg == "y";
|
||||||
|
|
||||||
|
… while evaluating the condition of the assertion '(arg == "y")'
|
||||||
|
at /pwd/lang/eval-fail-assert.nix:4:5:
|
||||||
|
3| arg:
|
||||||
|
4| assert arg == "y";
|
||||||
|
| ^
|
||||||
|
5| 123;
|
||||||
|
|
||||||
|
error: string '"x"' is not equal to string '"y"'
|
||||||
8
nix-js/tests/lang/eval-fail-assert.nix
Normal file
8
nix-js/tests/lang/eval-fail-assert.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
let {
|
||||||
|
x =
|
||||||
|
arg:
|
||||||
|
assert arg == "y";
|
||||||
|
123;
|
||||||
|
|
||||||
|
body = x "x";
|
||||||
|
}
|
||||||
21
nix-js/tests/lang/eval-fail-attr-name-type.err.exp
Normal file
21
nix-js/tests/lang/eval-fail-attr-name-type.err.exp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the attribute 'puppy."${key}"'
|
||||||
|
at /pwd/lang/eval-fail-attr-name-type.nix:3:5:
|
||||||
|
2| attrs = {
|
||||||
|
3| puppy.doggy = { };
|
||||||
|
| ^
|
||||||
|
4| };
|
||||||
|
|
||||||
|
… while evaluating an attribute name
|
||||||
|
at /pwd/lang/eval-fail-attr-name-type.nix:7:15:
|
||||||
|
6| in
|
||||||
|
7| attrs.puppy.${key}
|
||||||
|
| ^
|
||||||
|
8|
|
||||||
|
|
||||||
|
error: expected a string but found an integer: 1
|
||||||
|
at /pwd/lang/eval-fail-attr-name-type.nix:7:15:
|
||||||
|
6| in
|
||||||
|
7| attrs.puppy.${key}
|
||||||
|
| ^
|
||||||
|
8|
|
||||||
7
nix-js/tests/lang/eval-fail-attr-name-type.nix
Normal file
7
nix-js/tests/lang/eval-fail-attr-name-type.nix
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
let
|
||||||
|
attrs = {
|
||||||
|
puppy.doggy = { };
|
||||||
|
};
|
||||||
|
key = 1;
|
||||||
|
in
|
||||||
|
attrs.puppy.${key}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
error: undefined variable 'd'
|
||||||
|
at /pwd/lang/eval-fail-attrset-merge-drops-later-rec.nix:4:9:
|
||||||
|
3| a = rec {
|
||||||
|
4| c = d + 2;
|
||||||
|
| ^
|
||||||
|
5| d = 3;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
a.b = 1;
|
||||||
|
a = rec {
|
||||||
|
c = d + 2;
|
||||||
|
d = 3;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
.c
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating a path segment
|
||||||
|
at /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:2:
|
||||||
|
1| "${x: x}"
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:4»
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"${x: x}"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
error: path '/pwd/lang/fnord' does not exist
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"${./fnord}"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating a path segment
|
||||||
|
at /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:3:
|
||||||
|
1| ''${x: x}''
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:5»
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
''${x: x}''
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating a path segment
|
||||||
|
at /pwd/lang/eval-fail-bad-string-interpolation-4.nix:19:3:
|
||||||
|
18| # The error message should not be too long.
|
||||||
|
19| ''${pkgs}''
|
||||||
|
| ^
|
||||||
|
20|
|
||||||
|
|
||||||
|
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «8 attributes elided» }; «8 attributes elided» }; «8 attributes elided» }
|
||||||
19
nix-js/tests/lang/eval-fail-bad-string-interpolation-4.nix
Normal file
19
nix-js/tests/lang/eval-fail-bad-string-interpolation-4.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
let
|
||||||
|
# Basically a "billion laughs" attack, but toned down to simulated `pkgs`.
|
||||||
|
ha = x: y: {
|
||||||
|
a = x y;
|
||||||
|
b = x y;
|
||||||
|
c = x y;
|
||||||
|
d = x y;
|
||||||
|
e = x y;
|
||||||
|
f = x y;
|
||||||
|
g = x y;
|
||||||
|
h = x y;
|
||||||
|
j = x y;
|
||||||
|
};
|
||||||
|
has = ha (ha (ha (ha (x: x)))) "ha";
|
||||||
|
# A large structure that has already been evaluated.
|
||||||
|
pkgs = builtins.deepSeq has has;
|
||||||
|
in
|
||||||
|
# The error message should not be too long.
|
||||||
|
''${pkgs}''
|
||||||
14
nix-js/tests/lang/eval-fail-blackhole.err.exp
Normal file
14
nix-js/tests/lang/eval-fail-blackhole.err.exp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the attribute 'body'
|
||||||
|
at /pwd/lang/eval-fail-blackhole.nix:2:3:
|
||||||
|
1| let {
|
||||||
|
2| body = x;
|
||||||
|
| ^
|
||||||
|
3| x = y;
|
||||||
|
|
||||||
|
error: infinite recursion encountered
|
||||||
|
at /pwd/lang/eval-fail-blackhole.nix:3:7:
|
||||||
|
2| body = x;
|
||||||
|
3| x = y;
|
||||||
|
| ^
|
||||||
|
4| y = x;
|
||||||
5
nix-js/tests/lang/eval-fail-blackhole.nix
Normal file
5
nix-js/tests/lang/eval-fail-blackhole.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
let {
|
||||||
|
body = x;
|
||||||
|
x = y;
|
||||||
|
y = x;
|
||||||
|
}
|
||||||
10
nix-js/tests/lang/eval-fail-call-primop.err.exp
Normal file
10
nix-js/tests/lang/eval-fail-call-primop.err.exp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'length' builtin
|
||||||
|
at /pwd/lang/eval-fail-call-primop.nix:1:1:
|
||||||
|
1| builtins.length 1
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while evaluating the first argument passed to builtins.length
|
||||||
|
|
||||||
|
error: expected a list but found an integer: 1
|
||||||
1
nix-js/tests/lang/eval-fail-call-primop.nix
Normal file
1
nix-js/tests/lang/eval-fail-call-primop.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
builtins.length 1
|
||||||
20
nix-js/tests/lang/eval-fail-deepseq.err.exp
Normal file
20
nix-js/tests/lang/eval-fail-deepseq.err.exp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'deepSeq' builtin
|
||||||
|
at /pwd/lang/eval-fail-deepseq.nix:1:1:
|
||||||
|
1| builtins.deepSeq { x = abort "foo"; } 456
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while evaluating the attribute 'x'
|
||||||
|
at /pwd/lang/eval-fail-deepseq.nix:1:20:
|
||||||
|
1| builtins.deepSeq { x = abort "foo"; } 456
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
… while calling the 'abort' builtin
|
||||||
|
at /pwd/lang/eval-fail-deepseq.nix:1:24:
|
||||||
|
1| builtins.deepSeq { x = abort "foo"; } 456
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: evaluation aborted with the following error message: 'foo'
|
||||||
1
nix-js/tests/lang/eval-fail-deepseq.nix
Normal file
1
nix-js/tests/lang/eval-fail-deepseq.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
builtins.deepSeq { x = abort "foo"; } 456
|
||||||
26
nix-js/tests/lang/eval-fail-derivation-name.err.exp
Normal file
26
nix-js/tests/lang/eval-fail-derivation-name.err.exp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the attribute 'outPath'
|
||||||
|
at <nix/derivation-internal.nix>:<number>:<number>:
|
||||||
|
<number>| value = commonAttrs // {
|
||||||
|
<number>| outPath = builtins.getAttr outputName strict;
|
||||||
|
| ^
|
||||||
|
<number>| drvPath = strict.drvPath;
|
||||||
|
|
||||||
|
… while calling the 'getAttr' builtin
|
||||||
|
at <nix/derivation-internal.nix>:<number>:<number>:
|
||||||
|
<number>| value = commonAttrs // {
|
||||||
|
<number>| outPath = builtins.getAttr outputName strict;
|
||||||
|
| ^
|
||||||
|
<number>| drvPath = strict.drvPath;
|
||||||
|
|
||||||
|
… while calling the 'derivationStrict' builtin
|
||||||
|
at <nix/derivation-internal.nix>:<number>:<number>:
|
||||||
|
<number>|
|
||||||
|
<number>| strict = derivationStrict drvAttrs;
|
||||||
|
| ^
|
||||||
|
<number>|
|
||||||
|
|
||||||
|
… while evaluating derivation '~jiggle~'
|
||||||
|
whose name attribute is located at /pwd/lang/eval-fail-derivation-name.nix:<number>:<number>
|
||||||
|
|
||||||
|
error: invalid derivation name: name '~jiggle~' contains illegal character '~'. Please pass a different 'name'.
|
||||||
5
nix-js/tests/lang/eval-fail-derivation-name.nix
Normal file
5
nix-js/tests/lang/eval-fail-derivation-name.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
derivation {
|
||||||
|
name = "~jiggle~";
|
||||||
|
system = "some-system";
|
||||||
|
builder = "/dontcare";
|
||||||
|
}
|
||||||
14
nix-js/tests/lang/eval-fail-dup-dynamic-attrs.err.exp
Normal file
14
nix-js/tests/lang/eval-fail-dup-dynamic-attrs.err.exp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
error:
|
||||||
|
… while evaluating the attribute 'set'
|
||||||
|
at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:2:3:
|
||||||
|
1| {
|
||||||
|
2| set = {
|
||||||
|
| ^
|
||||||
|
3| "${"" + "b"}" = 1;
|
||||||
|
|
||||||
|
error: dynamic attribute 'b' already defined at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:3:5
|
||||||
|
at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:6:5:
|
||||||
|
5| set = {
|
||||||
|
6| "${"b" + ""}" = 2;
|
||||||
|
| ^
|
||||||
|
7| };
|
||||||
8
nix-js/tests/lang/eval-fail-dup-dynamic-attrs.nix
Normal file
8
nix-js/tests/lang/eval-fail-dup-dynamic-attrs.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
set = {
|
||||||
|
"${"" + "b"}" = 1;
|
||||||
|
};
|
||||||
|
set = {
|
||||||
|
"${"b" + ""}" = 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
51
nix-js/tests/lang/eval-fail-duplicate-traces.err.exp
Normal file
51
nix-js/tests/lang/eval-fail-duplicate-traces.err.exp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
error:
|
||||||
|
… from call site
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:6:1:
|
||||||
|
5| in
|
||||||
|
6| throwAfter 2
|
||||||
|
| ^
|
||||||
|
7|
|
||||||
|
|
||||||
|
… while calling 'throwAfter'
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
… from call site
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:33:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
… while calling 'throwAfter'
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
… from call site
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:33:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
… while calling 'throwAfter'
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
… while calling the 'throw' builtin
|
||||||
|
at /pwd/lang/eval-fail-duplicate-traces.nix:4:57:
|
||||||
|
3| let
|
||||||
|
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
| ^
|
||||||
|
5| in
|
||||||
|
|
||||||
|
error: Uh oh!
|
||||||
6
nix-js/tests/lang/eval-fail-duplicate-traces.nix
Normal file
6
nix-js/tests/lang/eval-fail-duplicate-traces.nix
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Check that we only omit duplicate stack traces when there's a bunch of them.
|
||||||
|
# Here, there's only a couple duplicate entries, so we output them all.
|
||||||
|
let
|
||||||
|
throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
|
||||||
|
in
|
||||||
|
throwAfter 2
|
||||||
6
nix-js/tests/lang/eval-fail-eol-1.err.exp
Normal file
6
nix-js/tests/lang/eval-fail-eol-1.err.exp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
error: undefined variable 'invalid'
|
||||||
|
at /pwd/lang/eval-fail-eol-1.nix:2:1:
|
||||||
|
1| # foo
|
||||||
|
2| invalid
|
||||||
|
| ^
|
||||||
|
3| # bar
|
||||||
3
nix-js/tests/lang/eval-fail-eol-1.nix
Normal file
3
nix-js/tests/lang/eval-fail-eol-1.nix
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# foo
|
||||||
|
invalid
|
||||||
|
# bar
|
||||||
6
nix-js/tests/lang/eval-fail-eol-2.err.exp
Normal file
6
nix-js/tests/lang/eval-fail-eol-2.err.exp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
error: undefined variable 'invalid'
|
||||||
|
at /pwd/lang/eval-fail-eol-2.nix:2:1:
|
||||||
|
1| # foo
|
||||||
|
2| invalid
|
||||||
|
| ^
|
||||||
|
3| # bar
|
||||||
2
nix-js/tests/lang/eval-fail-eol-2.nix
Normal file
2
nix-js/tests/lang/eval-fail-eol-2.nix
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# foo
|
||||||
|
invalid
|
||||||
6
nix-js/tests/lang/eval-fail-eol-3.err.exp
Normal file
6
nix-js/tests/lang/eval-fail-eol-3.err.exp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
error: undefined variable 'invalid'
|
||||||
|
at /pwd/lang/eval-fail-eol-3.nix:2:1:
|
||||||
|
1| # foo
|
||||||
|
2| invalid
|
||||||
|
| ^
|
||||||
|
3| # bar
|
||||||
3
nix-js/tests/lang/eval-fail-eol-3.nix
Normal file
3
nix-js/tests/lang/eval-fail-eol-3.nix
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# foo
|
||||||
|
invalid
|
||||||
|
# bar
|
||||||
8
nix-js/tests/lang/eval-fail-fetchTree-negative.err.exp
Normal file
8
nix-js/tests/lang/eval-fail-fetchTree-negative.err.exp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'fetchTree' builtin
|
||||||
|
at /pwd/lang/eval-fail-fetchTree-negative.nix:1:1:
|
||||||
|
1| builtins.fetchTree {
|
||||||
|
| ^
|
||||||
|
2| type = "file";
|
||||||
|
|
||||||
|
error: negative value given for 'fetchTree' argument 'owner': -1
|
||||||
5
nix-js/tests/lang/eval-fail-fetchTree-negative.nix
Normal file
5
nix-js/tests/lang/eval-fail-fetchTree-negative.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
builtins.fetchTree {
|
||||||
|
type = "file";
|
||||||
|
url = "file://eval-fail-fetchTree-negative.nix";
|
||||||
|
owner = -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'fetchurl' builtin
|
||||||
|
at /pwd/lang/eval-fail-fetchurl-baseName-attrs-name.nix:1:1:
|
||||||
|
1| builtins.fetchurl {
|
||||||
|
| ^
|
||||||
|
2| url = "https://example.com/foo.tar.gz";
|
||||||
|
|
||||||
|
error: invalid store path name when fetching URL 'https://example.com/foo.tar.gz': name '~wobble~' contains illegal character '~'. Please change the value for the 'name' attribute passed to 'fetchurl', so that it can create a valid store path.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
builtins.fetchurl {
|
||||||
|
url = "https://example.com/foo.tar.gz";
|
||||||
|
name = "~wobble~";
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'fetchurl' builtin
|
||||||
|
at /pwd/lang/eval-fail-fetchurl-baseName-attrs.nix:1:1:
|
||||||
|
1| builtins.fetchurl { url = "https://example.com/~wiggle~"; }
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: invalid store path name when fetching URL 'https://example.com/~wiggle~': name '~wiggle~' contains illegal character '~'. Please add a valid 'name' attribute to the argument for 'fetchurl', so that it can create a valid store path.
|
||||||
1
nix-js/tests/lang/eval-fail-fetchurl-baseName-attrs.nix
Normal file
1
nix-js/tests/lang/eval-fail-fetchurl-baseName-attrs.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
builtins.fetchurl { url = "https://example.com/~wiggle~"; }
|
||||||
8
nix-js/tests/lang/eval-fail-fetchurl-baseName.err.exp
Normal file
8
nix-js/tests/lang/eval-fail-fetchurl-baseName.err.exp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
error:
|
||||||
|
… while calling the 'fetchurl' builtin
|
||||||
|
at /pwd/lang/eval-fail-fetchurl-baseName.nix:1:1:
|
||||||
|
1| builtins.fetchurl "https://example.com/~wiggle~"
|
||||||
|
| ^
|
||||||
|
2|
|
||||||
|
|
||||||
|
error: invalid store path name when fetching URL 'https://example.com/~wiggle~': name '~wiggle~' contains illegal character '~'. Please pass an attribute set with 'url' and 'name' attributes to 'fetchurl', so that it can create a valid store path.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user