-
Notifications
You must be signed in to change notification settings - Fork 0
Sync with upstream (2026-04-07) #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6ee502a
1e61578
cf0e8ff
fbf5fab
7de15fa
c90a034
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,35 @@ use std::error::Error; | |
| use std::ffi::{c_int, c_void, CStr, CString}; | ||
| use std::ptr::{null, null_mut}; | ||
|
|
||
| /// A primop error that is not memoized in the thunk that triggered it, | ||
| /// allowing the thunk to be forced again. | ||
| /// | ||
| /// Since [Nix 2.34](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes), | ||
| /// primop errors are memoized by default: once a thunk fails, forcing it | ||
| /// again returns the same error. Use `RecoverableError` for errors that | ||
| /// are transient, so the caller can retry. | ||
| /// | ||
| /// On Nix < 2.34, all errors are already recoverable, so this type has | ||
| /// no additional effect. | ||
| /// | ||
| /// Available since nix-bindings-expr 0.2.1. | ||
| #[derive(Debug)] | ||
| pub struct RecoverableError(String); | ||
|
|
||
| impl RecoverableError { | ||
| pub fn new(msg: impl Into<String>) -> Self { | ||
| RecoverableError(msg.into()) | ||
| } | ||
| } | ||
|
|
||
| impl std::fmt::Display for RecoverableError { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| self.0.fmt(f) | ||
| } | ||
| } | ||
|
|
||
| impl std::error::Error for RecoverableError {} | ||
|
|
||
| /// Metadata for a primop, used with `PrimOp::new`. | ||
| pub struct PrimOpMeta<'a, const N: usize> { | ||
| /// Name of the primop. Note that primops do not have to be registered as | ||
|
|
@@ -35,7 +64,13 @@ impl Drop for PrimOp<'_> { | |
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'a> PrimOp<'a> { | ||
| /// Create a new primop with the given metadata and implementation. | ||
| /// | ||
| /// When `f` returns an `Err`, the error is propagated to the Nix evaluator. | ||
| /// To return a [recoverable error](RecoverableError), include it in the | ||
| /// error chain (e.g. `Err(RecoverableError::new("...").into())`). | ||
| pub fn new<const N: usize>( | ||
| eval_state: &'a mut EvalState, | ||
| meta: PrimOpMeta<N>, | ||
|
|
@@ -134,13 +169,24 @@ unsafe extern "C" fn function_adapter( | |
| raw::copy_value(context_out, ret, v.raw_ptr()); | ||
| }, | ||
| Err(e) => unsafe { | ||
| let cstr = CString::new(e.to_string()).unwrap_or_else(|_e| { | ||
| let err_str = e.to_string(); | ||
| let err_code = error_code(e); | ||
| let cstr = CString::new(err_str).unwrap_or_else(|_e| { | ||
| CString::new("<rust nix-expr application error message contained null byte>") | ||
| .unwrap() | ||
| }); | ||
| raw_util::set_err_msg(context_out, raw_util::err_NIX_ERR_UNKNOWN, cstr.as_ptr()); | ||
| raw_util::set_err_msg(context_out, err_code, cstr.as_ptr()); | ||
| }, | ||
|
Comment on lines
+172
to
179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify current implementation only checks top-level error type and does not walk source() chain.
rg -n -C3 'fn error_code|downcast_ref::<RecoverableError>|source\(' nix-bindings-expr/src/primop.rsRepository: DeterminateSystems/nix-bindings-rust Length of output: 415 🏁 Script executed: #!/bin/bash
# 1. Search for RecoverableError definition and usage
rg -n 'RecoverableError' nix-bindings-expr/src/ -B2 -A5Repository: DeterminateSystems/nix-bindings-rust Length of output: 5570 🏁 Script executed: #!/bin/bash
# 2. Check how errors are created/wrapped in function_adapter
rg -n 'function_adapter|Box::new.*Error|anyhow::anyhow' nix-bindings-expr/src/primop.rs -B3 -A3Repository: DeterminateSystems/nix-bindings-rust Length of output: 426 🏁 Script executed: #!/bin/bash
# 3. Verify Error::source() method and error chain patterns
rg -n 'Error|source|chain' nix-bindings-expr/src/primop.rs | head -30Repository: DeterminateSystems/nix-bindings-rust Length of output: 1008 🏁 Script executed: #!/bin/bash
# 4. Check the full error_code function and surrounding context
sed -n '170,195p' nix-bindings-expr/src/primop.rsRepository: DeterminateSystems/nix-bindings-rust Length of output: 866 🏁 Script executed: #!/bin/bash
# Check if Error::source() is used anywhere in the codebase
rg -n 'source\(\)' nix-bindings-expr/src/Repository: DeterminateSystems/nix-bindings-rust Length of output: 62 🏁 Script executed: #!/bin/bash
# Look at the test more carefully - specifically the wrapped error case
sed -n '2950,2980p' nix-bindings-expr/src/eval_state.rsRepository: DeterminateSystems/nix-bindings-rust Length of output: 1211 🏁 Script executed: #!/bin/bash
# Check what the .context() method does and what error type chain it creates
rg -n 'use.*anyhow|use.*error|Context' nix-bindings-expr/src/primop.rs | head -20Repository: DeterminateSystems/nix-bindings-rust Length of output: 344 🏁 Script executed: #!/bin/bash
# Check if anyhow is being used and how errors are wrapped
rg -n 'anyhow|chain|context|source' nix-bindings-expr/src/ -i | head -30Repository: DeterminateSystems/nix-bindings-rust Length of output: 2952 🏁 Script executed: #!/bin/bash
# Check the imports and dependencies
cat nix-bindings-expr/Cargo.toml | grep -A 20 '\[dependencies\]'Repository: DeterminateSystems/nix-bindings-rust Length of output: 823 🏁 Script executed: #!/bin/bash
# Look at actual primop implementations to see error patterns
rg -n 'Err\(' nix-bindings-expr/src/ -B1 -A1 | head -50Repository: DeterminateSystems/nix-bindings-rust Length of output: 3248 Recoverable-error detection currently misses wrapped errors.
The test at 💡 Proposed fix- let err_str = e.to_string();
- let err_code = error_code(e);
+ let err_code = error_code(e.as_ref());
+ let err_str = e.to_string();
let cstr = CString::new(err_str).unwrap_or_else(|_e| {
CString::new("<rust nix-expr application error message contained null byte>")
.unwrap()
});
raw_util::set_err_msg(context_out, err_code, cstr.as_ptr());
@@
#[cfg_attr(not(nix_at_least = "2.34.0pre"), allow(unused))]
-fn error_code(e: Box<dyn Error>) -> raw_util::err {
+fn error_code(e: &(dyn Error + 'static)) -> raw_util::err {
#[cfg(nix_at_least = "2.34.0pre")]
- if e.downcast_ref::<RecoverableError>().is_some() {
- return raw_util::err_NIX_ERR_RECOVERABLE;
+ {
+ if e.downcast_ref::<RecoverableError>().is_some() {
+ return raw_util::err_NIX_ERR_RECOVERABLE;
+ }
+ let mut source = e.source();
+ while let Some(err) = source {
+ if err.downcast_ref::<RecoverableError>().is_some() {
+ return raw_util::err_NIX_ERR_RECOVERABLE;
+ }
+ source = err.source();
+ }
}
raw_util::err_NIX_ERR_UNKNOWN
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| #[cfg_attr(not(nix_at_least = "2.34.0pre"), allow(unused))] | ||
| fn error_code(e: Box<dyn Error>) -> raw_util::err { | ||
| #[cfg(nix_at_least = "2.34.0pre")] | ||
| if e.downcast_ref::<RecoverableError>().is_some() { | ||
| return raw_util::err_NIX_ERR_RECOVERABLE; | ||
| } | ||
| raw_util::err_NIX_ERR_UNKNOWN | ||
| } | ||
|
|
||
| static FUNCTION_ADAPTER: raw::PrimOpFun = Some(function_adapter); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: DeterminateSystems/nix-bindings-rust
Length of output: 115
🏁 Script executed:
Repository: DeterminateSystems/nix-bindings-rust
Length of output: 120
🏁 Script executed:
sed -n '2940,2980p' nix-bindings-expr/src/eval_state.rsRepository: DeterminateSystems/nix-bindings-rust
Length of output: 1485
🏁 Script executed:
Repository: DeterminateSystems/nix-bindings-rust
Length of output: 1659
🏁 Script executed:
Repository: DeterminateSystems/nix-bindings-rust
Length of output: 383
Test doesn't actually verify error chain behavior.
The closure returns
Err(primop::RecoverableError::new(...).into()), which is the same direct conversion as the preceding test. SinceRecoverableErrorimplementsErrorwith nosource()method, this produces a top-level error with no chain—not the error-chain scenario the test name suggests.To match the test's intent, introduce a wrapper error that chains
RecoverableErrorviasource():Suggested test adjustment
#[test] #[cfg(nix_at_least = "2.34.0pre")] fn eval_state_primop_recoverable_error_in_chain() { gc_registering_current_thread(|| { + #[derive(Debug)] + struct WrappedRecoverable(primop::RecoverableError); + impl std::fmt::Display for WrappedRecoverable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "wrapped recoverable") + } + } + impl std::error::Error for WrappedRecoverable { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.0) + } + } + let store = Store::open(None, []).unwrap(); let mut es = EvalState::new(store, []).unwrap(); @@ call_count.set(count + 1); if count == 0 { - // Wrap RecoverableError in .context(), pushing it down the chain - Err(primop::RecoverableError::new("transient failure").into()) + Err(Box::new(WrappedRecoverable( + primop::RecoverableError::new("transient failure"), + ))) } else { es.new_value_int(42) }🤖 Prompt for AI Agents