Skip to content

Conversation

@lionel-
Copy link
Member

@lionel- lionel- commented Feb 5, 2026

Follow up to #1871

@DavisVaughan DavisVaughan self-requested a review February 5, 2026 22:41
Copy link
Member

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few notes from our offline conversation for future us:

  • r_env_get() has always triggered active bindings, due to how Rf_findVarInFrame3() also triggers active bindings. This feels right to us. If you need to specially handle active, delayed, or forced bindings then you should switch on the binding type up front.

  • An emerging pattern is moving from a single r_env_find() call to r_env_has() + r_env_get()

  • It's unfortunate that R_getVar() errors on R_MissingArg bindings. This is a valid symbol to bind into an environment, and it feels like a shame that you can't pluck it out of there as well. It's certainly not "unbound". It feels like only Rf_eval() should care about erroring on R_MissingArg. If R_getVar() allowed R_MissingArg, then r_env_get() could have just used that directly, but without that it feels like we will never be able to make this switch.

Comment on lines +169 to +170
r_obj* promise = KEEP(Rf_allocSExp(PROMSXP));
SET_PRCODE(promise, expr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were KEEP()ing expr here before the allocation, I think we probably want that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm rchk wouldn't be happy with that (relying on an internal protect), and it seems weird to protect expr but not other arguments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait regarding rchk I think I'm confusing something else, i.e. that the argument is considered protected after the scope has finished.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like in R_MakeDelayedBinding we aren't protecting anything directly
https://github.com/r-devel/r-svn/pull/206/changes#diff-5272e85b221848a45cc500d6792e9aba179ddbbaa817e48757b686deead11245R3502

But Rf_mkPROMISE protects both expr and evalEnv internally.


Then for R_MakeForcedBinding it looks like that drops into R_mkEVPROMISE which only protects the expr and not the value via an internal call to mkPROMISE (which we determined protects expr)
https://github.com/r-devel/r-svn/blob/f98b44aeb0f6b28891d5b1febfdaea52c2a5647a/src/main/memory.c#L2646

This is quite challenging to wrap my head around


r_obj* promise = r_env_find(env, sym);
SET_PRVALUE(promise, value);
r_obj* promise = KEEP(Rf_allocSExp(PROMSXP));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about KEEP(expr), and idk if we want to KEEP(value) too or not?

I don't remember what we made R_MakeForcedBinding did

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an assignment operation can't protect two values at the same time because that would mean that the first temporary object would be unprotected while the second is created in:

bind(x, foo(), bar())

@lionel-
Copy link
Member Author

lionel- commented Feb 10, 2026

We decided to embrace the explicit protection pattern when assigning.

Before:

obj_set(x, new());

After:

obj_set(x, KEEP(new());
FREE(1);

The need to free after the assignment is especially annoying, but there's no way to implement a fully general assignment principle without getting into rchk complications. And the principle breaks down if more than one object are assigned. And without the explicit protection, there's always a small doubt in the back of the head when reading code compelling you to double check what the assignment does.

@lionel- lionel- merged commit ea03d3f into r-lib:main Feb 10, 2026
13 checks passed
@lionel- lionel- deleted the task/find-var branch February 10, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants