feat: builtins.unsafeGetAttrPos & __curPos
This commit is contained in:
@@ -140,3 +140,22 @@ export const zipAttrsWith =
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const unsafeGetAttrPos =
|
||||
(attrName: NixValue) =>
|
||||
(attrSet: NixValue): NixValue => {
|
||||
const name = forceStringValue(attrName);
|
||||
const attrs = forceAttrs(attrSet);
|
||||
|
||||
if (!(name in attrs)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const positions = (attrs as any)[Nix.ATTR_POSITIONS];
|
||||
if (!positions || !(name in positions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const span = positions[name];
|
||||
return Nix.mkPos(span);
|
||||
};
|
||||
|
||||
@@ -173,6 +173,7 @@ export const builtins: any = {
|
||||
catAttrs: mkPrimop(attrs.catAttrs, "catAttrs", 2),
|
||||
groupBy: mkPrimop(attrs.groupBy, "groupBy", 2),
|
||||
zipAttrsWith: mkPrimop(attrs.zipAttrsWith, "zipAttrsWith", 2),
|
||||
unsafeGetAttrPos: mkPrimop(attrs.unsafeGetAttrPos, "unsafeGetAttrPos", 2),
|
||||
|
||||
stringLength: mkPrimop(string.stringLength, "stringLength", 1),
|
||||
substring: mkPrimop(string.substring, "substring", 3),
|
||||
@@ -232,7 +233,6 @@ export const builtins: any = {
|
||||
1,
|
||||
),
|
||||
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
|
||||
unsafeGetAttrPos: mkPrimop(misc.unsafeGetAttrPos, "unsafeGetAttrPos", 2),
|
||||
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
|
||||
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
|
||||
flakeRefToString: mkPrimop(misc.flakeRefToString, "flakeRefToString", 1),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { createThunk, force } from "../thunk";
|
||||
import { CatchableError } from "../types";
|
||||
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
||||
import { forceList, forceAttrs, forceFunction, forceStringValue, forceString } from "../type-assert";
|
||||
import { forceList, forceAttrs, forceFunction, forceStringValue, forceString, forceStringNoCtx } from "../type-assert";
|
||||
import * as context from "./context";
|
||||
import { compareValues } from "../operators";
|
||||
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
|
||||
@@ -50,10 +50,6 @@ export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependen
|
||||
|
||||
export const unsafeDiscardStringContext = context.unsafeDiscardStringContext;
|
||||
|
||||
export const unsafeGetAttrPos = (s: NixValue): never => {
|
||||
throw new Error("Not implemented: unsafeGetAttrPos");
|
||||
};
|
||||
|
||||
export const addDrvOutputDependencies = context.addDrvOutputDependencies;
|
||||
|
||||
export const compareVersions =
|
||||
|
||||
@@ -388,3 +388,7 @@ export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
|
||||
}
|
||||
return alter;
|
||||
};
|
||||
|
||||
export const mkPos = (span: string): NixAttrs => {
|
||||
return Deno.core.ops.op_decode_span(span);
|
||||
};
|
||||
|
||||
@@ -18,12 +18,13 @@ import {
|
||||
pushContext,
|
||||
popContext,
|
||||
withContext,
|
||||
mkPos,
|
||||
} from "./helpers";
|
||||
import { op } from "./operators";
|
||||
import { builtins, PRIMOP_METADATA } from "./builtins";
|
||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||
import { HAS_CONTEXT } from "./string-context";
|
||||
import { IS_PATH, mkAttrs, mkFunction } from "./types";
|
||||
import { IS_PATH, mkAttrs, mkFunction, mkAttrsWithPos, ATTR_POSITIONS } from "./types";
|
||||
import { forceBool } from "./type-assert";
|
||||
|
||||
export type NixRuntime = typeof Nix;
|
||||
@@ -53,7 +54,10 @@ export const Nix = {
|
||||
concatStringsWithContext,
|
||||
StringCoercionMode,
|
||||
mkAttrs,
|
||||
mkAttrsWithPos,
|
||||
mkFunction,
|
||||
mkPos,
|
||||
ATTR_POSITIONS,
|
||||
|
||||
pushContext,
|
||||
popContext,
|
||||
|
||||
@@ -87,6 +87,39 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const ATTR_POSITIONS = Symbol("attrPositions");
|
||||
|
||||
export const mkAttrsWithPos = (
|
||||
attrs: NixAttrs,
|
||||
positions: Record<string, string>,
|
||||
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] }
|
||||
): NixAttrs => {
|
||||
if (dyns) {
|
||||
const len = dyns.dynKeys.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const key = force(dyns.dynKeys[i]);
|
||||
if (key === null) {
|
||||
continue;
|
||||
}
|
||||
const str = forceStringNoCtx(key);
|
||||
attrs[str] = dyns.dynVals[i];
|
||||
positions[str] = dyns.dynSpans[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(positions).length > 0) {
|
||||
Object.defineProperty(attrs, ATTR_POSITIONS, {
|
||||
value: positions,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
export { ATTR_POSITIONS };
|
||||
|
||||
/**
|
||||
* Interface for lazy thunk values
|
||||
* Thunks delay evaluation until forced
|
||||
|
||||
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -42,6 +42,7 @@ declare global {
|
||||
function op_read_dir(path: string): Record<string, string>;
|
||||
function op_path_exists(path: string): boolean;
|
||||
function op_sha256_hex(data: string): string;
|
||||
function op_decode_span(span: string): { file: string | null; line: number | null; column: number | null };
|
||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
||||
function op_output_path_name(drv_name: string, output_name: string): string;
|
||||
function op_make_fixed_output_path(
|
||||
|
||||
@@ -171,6 +171,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||
)
|
||||
}
|
||||
}
|
||||
Ir::CurPos(cur_pos) => {
|
||||
let span_str = encode_span(cur_pos.span, ctx);
|
||||
format!("Nix.mkPos({})", span_str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,9 +350,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||
fn compile(&self, ctx: &Ctx) -> String {
|
||||
let mut attrs = Vec::new();
|
||||
let mut attr_positions = Vec::new();
|
||||
let stack_trace_enabled = std::env::var("NIX_JS_STACK_TRACE").is_ok();
|
||||
|
||||
for (&sym, &expr) in &self.stcs {
|
||||
for (&sym, &(expr, attr_span)) in &self.stcs {
|
||||
let key = ctx.get_sym(sym);
|
||||
let value_code = ctx.get_ir(expr).compile(ctx);
|
||||
|
||||
@@ -362,12 +367,15 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||
value_code
|
||||
};
|
||||
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
||||
|
||||
let attr_pos_str = encode_span(attr_span, ctx);
|
||||
attr_positions.push(format!("{}:{}", key.escape_quote(), attr_pos_str));
|
||||
}
|
||||
|
||||
if !self.dyns.is_empty() {
|
||||
let (keys, vals) = self.dyns.iter().map(|&(key, val)| {
|
||||
let key = ctx.get_ir(key).compile(ctx);
|
||||
let val_expr = ctx.get_ir(val);
|
||||
let (keys, vals, dyn_spans) = self.dyns.iter().map(|(key, val, attr_span)| {
|
||||
let key = ctx.get_ir(*key).compile(ctx);
|
||||
let val_expr = ctx.get_ir(*val);
|
||||
let val = val_expr.compile(ctx);
|
||||
let span = val_expr.span();
|
||||
let val = if stack_trace_enabled {
|
||||
@@ -379,9 +387,19 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||
} else {
|
||||
val
|
||||
};
|
||||
(key, val)
|
||||
}).collect::<(Vec<_>, Vec<_>)>();
|
||||
format!("Nix.mkAttrs({{{}}},[{}],[{}])", attrs.join(","), keys.join(","), vals.join(","))
|
||||
let dyn_span_str = encode_span(*attr_span, ctx);
|
||||
(key, val, dyn_span_str)
|
||||
}).multiunzip::<(Vec<_>, Vec<_>, Vec<_>)>();
|
||||
format!(
|
||||
"Nix.mkAttrsWithPos({{{}}},{{{}}},{{dynKeys:[{}],dynVals:[{}],dynSpans:[{}]}})",
|
||||
attrs.join(","),
|
||||
attr_positions.join(","),
|
||||
keys.join(","),
|
||||
vals.join(","),
|
||||
dyn_spans.join(",")
|
||||
)
|
||||
} else if !attr_positions.is_empty() {
|
||||
format!("Nix.mkAttrsWithPos({{{}}},{{{}}})", attrs.join(","), attr_positions.join(","))
|
||||
} else {
|
||||
format!("{{{}}}", attrs.join(","))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ ir! {
|
||||
Bool(bool),
|
||||
Null,
|
||||
Str { pub val: String },
|
||||
AttrSet { pub stcs: HashMap<SymId, ExprId>, pub dyns: Vec<(ExprId, ExprId)> },
|
||||
AttrSet { pub stcs: HashMap<SymId, (ExprId, rnix::TextRange)>, pub dyns: Vec<(ExprId, ExprId, rnix::TextRange)> },
|
||||
List { pub items: Vec<ExprId> },
|
||||
|
||||
HasAttr { pub lhs: ExprId, pub rhs: Vec<Attr> },
|
||||
@@ -78,6 +78,7 @@ ir! {
|
||||
Thunk(ExprId),
|
||||
Builtins,
|
||||
Builtin(SymId),
|
||||
CurPos,
|
||||
}
|
||||
|
||||
impl Ir {
|
||||
@@ -106,6 +107,7 @@ impl Ir {
|
||||
Ir::Thunk(t) => t.span,
|
||||
Ir::Builtins(b) => b.span,
|
||||
Ir::Builtin(b) => b.span,
|
||||
Ir::CurPos(c) => c.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +125,7 @@ impl AttrSet {
|
||||
match attr {
|
||||
Attr::Str(ident, span) => {
|
||||
// If the next attribute is a static string.
|
||||
if let Some(&id) = self.stcs.get(&ident) {
|
||||
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
|
||||
@@ -151,8 +153,8 @@ impl AttrSet {
|
||||
span,
|
||||
};
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
let attrs = ctx.new_expr(attrs.to_ir());
|
||||
self.stcs.insert(ident, attrs);
|
||||
let attrs_expr = ctx.new_expr(attrs.to_ir());
|
||||
self.stcs.insert(ident, (attrs_expr, span));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -166,7 +168,7 @@ impl AttrSet {
|
||||
span,
|
||||
};
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir())));
|
||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir()), span));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -174,7 +176,7 @@ impl AttrSet {
|
||||
// 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).is_some() {
|
||||
if self.stcs.insert(ident, (value, span)).is_some() {
|
||||
return Err(Error::downgrade_error(
|
||||
format!(
|
||||
"attribute '{}' already defined",
|
||||
@@ -185,8 +187,8 @@ impl AttrSet {
|
||||
));
|
||||
}
|
||||
}
|
||||
Attr::Dynamic(dynamic, _) => {
|
||||
self.dyns.push((dynamic, value));
|
||||
Attr::Dynamic(dynamic, span) => {
|
||||
self.dyns.push((dynamic, value, span));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -174,9 +174,15 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let sym = self.ident_token().unwrap().to_string();
|
||||
let sym = ctx.new_sym(sym);
|
||||
ctx.lookup(sym, self.syntax().text_range())
|
||||
let text = self.ident_token().unwrap().to_string();
|
||||
let span = self.syntax().text_range();
|
||||
|
||||
if text == "__curPos" {
|
||||
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
|
||||
}
|
||||
|
||||
let sym = ctx.new_sym(text);
|
||||
ctx.lookup(sym, span)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +209,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
||||
for sym in binding_keys {
|
||||
// FIXME: span
|
||||
let expr = ctx.lookup(*sym, synthetic_span())?;
|
||||
attrs.stcs.insert(*sym, expr);
|
||||
attrs.stcs.insert(*sym, (expr, synthetic_span()));
|
||||
}
|
||||
|
||||
Ok(ctx.new_expr(attrs.to_ir()))
|
||||
@@ -325,7 +331,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||
for sym in binding_keys {
|
||||
// FIXME: span
|
||||
let expr = ctx.lookup(sym, synthetic_span())?;
|
||||
attrs.stcs.insert(sym, expr);
|
||||
attrs.stcs.insert(sym, (expr, synthetic_span()));
|
||||
}
|
||||
|
||||
Result::Ok(ctx.new_expr(attrs.to_ir()))
|
||||
@@ -334,7 +340,6 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||
let body_sym = ctx.new_sym("body".to_string());
|
||||
let select = Select {
|
||||
expr: attrset_expr,
|
||||
// FIXME: span
|
||||
attrpath: vec![Attr::Str(body_sym, synthetic_span())],
|
||||
default: None,
|
||||
span,
|
||||
|
||||
@@ -59,7 +59,7 @@ pub fn downgrade_static_attrs(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs.stcs)
|
||||
Ok(attrs.stcs.into_iter().map(|(k, (v, _))| (k, v)).collect())
|
||||
}
|
||||
|
||||
/// Downgrades an `inherit` statement.
|
||||
@@ -67,7 +67,7 @@ pub fn downgrade_static_attrs(
|
||||
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
||||
pub fn downgrade_inherit(
|
||||
inherit: ast::Inherit,
|
||||
stcs: &mut HashMap<SymId, ExprId>,
|
||||
stcs: &mut HashMap<SymId, (ExprId, rnix::TextRange)>,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
// Downgrade the `from` expression if it exists.
|
||||
@@ -122,7 +122,7 @@ pub fn downgrade_inherit(
|
||||
.with_span(span)
|
||||
.with_source(ctx.get_current_source()));
|
||||
}
|
||||
Entry::Vacant(vacant) => vacant.insert(expr),
|
||||
Entry::Vacant(vacant) => vacant.insert((expr, span)),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
@@ -534,7 +534,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
Ok(temp_attrs.stcs)
|
||||
Ok(temp_attrs.stcs.into_iter().map(|(k, (v, _))| (k, v)).collect())
|
||||
},
|
||||
body_fn,
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
||||
op_path_exists(),
|
||||
op_resolve_path(),
|
||||
op_sha256_hex(),
|
||||
op_decode_span::<Ctx>(),
|
||||
op_make_store_path::<Ctx>(),
|
||||
op_output_path_name(),
|
||||
op_make_fixed_output_path::<Ctx>(),
|
||||
@@ -268,6 +269,56 @@ fn op_sha256_hex(#[string] data: String) -> String {
|
||||
crate::nix_hash::sha256_hex(&data)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[serde]
|
||||
fn op_decode_span<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] span_str: String,
|
||||
) -> std::result::Result<serde_json::Value, NixError> {
|
||||
let parts: Vec<&str> = span_str.split(':').collect();
|
||||
if parts.len() != 3 {
|
||||
return Ok(serde_json::json!({
|
||||
"file": serde_json::Value::Null,
|
||||
"line": serde_json::Value::Null,
|
||||
"column": serde_json::Value::Null
|
||||
}));
|
||||
}
|
||||
|
||||
let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?;
|
||||
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?;
|
||||
|
||||
let ctx: &Ctx = state.get_ctx();
|
||||
let source = ctx.get_source(source_id);
|
||||
let content = &source.src;
|
||||
|
||||
let (line, column) = byte_offset_to_line_col(content, start as usize);
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"file": source.get_name(),
|
||||
"line": line,
|
||||
"column": column
|
||||
}))
|
||||
}
|
||||
|
||||
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
||||
let mut line = 1u32;
|
||||
let mut col = 1u32;
|
||||
|
||||
for (idx, ch) in content.char_indices() {
|
||||
if idx >= offset {
|
||||
break;
|
||||
}
|
||||
if ch == '\n' {
|
||||
line += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(line, col)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_make_store_path<Ctx: RuntimeContext>(
|
||||
|
||||
Reference in New Issue
Block a user