[education/rkward/releases/0.8.3] rkward/rbackend: Adapt to removal/rename of promise-handling functions

Thomas Friedrichsmeier null at kde.org
Fri Apr 17 16:32:41 BST 2026


Git commit 9dbaa5068608e9e668491c32d80e86ebd931e905 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 15:31.
Pushed by tfry into branch 'releases/0.8.3'.

Adapt to removal/rename of promise-handling functions

M  +10   -1    rkward/rbackend/rkrapi.cpp
M  +20   -6    rkward/rbackend/rkrapi.h
M  +1    -2    rkward/rbackend/rkrbackend.cpp
M  +27   -6    rkward/rbackend/rkstructuregetter.cpp
M  +1    -1    rkward/rbackend/rkstructuregetter.h

https://invent.kde.org/education/rkward/-/commit/9dbaa5068608e9e668491c32d80e86ebd931e905

diff --git a/rkward/rbackend/rkrapi.cpp b/rkward/rbackend/rkrapi.cpp
index 979b2b0ad..eb56a1c05 100644
--- a/rkward/rbackend/rkrapi.cpp
+++ b/rkward/rbackend/rkrapi.cpp
@@ -34,7 +34,16 @@ void RFn::init(void *libr_dll_handle, void *(*dlsym_fun)(void *, const char *))
 	// work around various incompatiblities between R versions
 	// R < 4.5.0
 	if (!R_ClosureFormals) {
-		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
 		R_ClosureFormals = reinterpret_cast<decltype(R_ClosureFormals)>(dlsym_fun(libr_dll_handle, "FORMALS"));
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
 	}
+#if R_VERSION != R_Version(4, 6, 0)
+	if (!R_GetBindingType) {
+		PRCODE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRCODE"));
+		PRENV = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRENV"));
+		PRVALUE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRVALUE"));
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_GetBindingType to PRCODE (%p)", PRCODE);
+		RK_ASSERT(PRCODE);
+	}
+#endif
 }
diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index c3b144934..8e7ba33fe 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -177,16 +177,30 @@ class RFn : public QObject {
 #if (R_VERSION >= R_Version(4, 5, 0))
 	IMPORT_R_API(R_ClosureFormals);
 #else
-	// will be handled, manually, in init
-	static SEXP (*R_ClosureFormals)(SEXP);
+	// used to be named FORMALS; will be handled in init, manually
+	static inline SEXP (*R_ClosureFormals)(SEXP);
+#endif
+#if (R_VERSION >= R_Version(4, 6, 0))
+	IMPORT_R_API(R_GetBindingType);
+	//IMPORT_R_API(R_ActiveBindingFunction);
+	IMPORT_R_API(R_DelayedBindingExpression);
+	IMPORT_R_API(R_DelayedBindingEnvironment);
+	//IMPORT_R_API(R_ForcedBindingExpression);
+	IMPORT_R_API(R_getVar);
+	// these were removed in R 4.6, but we still need them
+	// if using a lower R at runtime
+	static inline SEXP (*PRCODE)(SEXP) = nullptr;
+	static inline SEXP (*PRENV)(SEXP) = nullptr;
+	static inline SEXP (*PRVALUE)(SEXP) = nullptr;
+#else
+	IMPORT_R_API(PRCODE);
+	IMPORT_R_API(PRENV);
+	IMPORT_R_API(PRVALUE);
 #endif
 	IMPORT_R_API(INTEGER);
 	IMPORT_R_API(LOGICAL);
 	IMPORT_R_API(LENGTH);
-	IMPORT_R_API(PRCODE);
-	IMPORT_R_API(PRENV);
 	IMPORT_R_API(PRINTNAME);
-	IMPORT_R_API(PRVALUE);
 	IMPORT_R_API(REAL);
 	IMPORT_R_API(R_CheckDeviceAvailable);
 	IMPORT_R_API(R_CheckStack);
@@ -293,7 +307,7 @@ class RFn : public QObject {
 	IMPORT_R_API(Riconv_close);
 	IMPORT_R_API(Riconv_open);
 	IMPORT_R_API(SET_TAG);
-	IMPORT_R_API(SET_TYPEOF);
+	IMPORT_R_API(Rf_lcons);
 	IMPORT_R_API(STRING_ELT);
 	IMPORT_R_API(SET_STRING_ELT);
 	IMPORT_R_API(SET_VECTOR_ELT);
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index e69ec744c..190ad9f58 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -1207,8 +1207,7 @@ RCommandProxy *RKRBackend::runDirectCommand(const QString &command, RCommand::Co
 
 static void setWarnOption(int level, bool tryeval = false) {
 	SEXP s, t;
-	RFn::Rf_protect(t = s = RFn::Rf_allocList(2));
-	RFn::SET_TYPEOF(s, LANGSXP);
+	RFn::Rf_protect(t = s = RFn::Rf_lcons(ROb(R_NilValue), RFn::Rf_allocList(2)));
 	RFn::SETCAR(t, RFn::Rf_install("options"));
 	t = RFn::CDR(t);
 	RFn::SETCAR(t, RFn::Rf_ScalarInteger(level));
diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index d1f079431..a44161c62 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -125,19 +125,40 @@ void RKStructureGetter::getStructureSafe(SEXP value, const QString &name, int ad
 	}
 }
 
-/** Temporarily resolve a promise, usually without keeping its value (unless keep_evalled_promises is set, which it never is, at the time of this writing).
+/** Get a symbol value from an environment, taking care not to (permanently) for promises (unless keep_evalled_promises is set,
+ *  which it never is, at the time of this writing).
  *  This is useful for peeking into large objects while building the object tree, without permanently using lots of RAM.
  *
  *  @note This is is not quite perfect, however. E.g. if we have two promises a and b, where b takes a slice out of a, then
  *        evaluating b will force a, permanently. */
-SEXP RKStructureGetter::resolvePromise(SEXP from, SEXP env) {
+SEXP RKStructureGetter::peekFromEnv(SEXP sym, SEXP env) {
 	RK_TRACE(RBACKEND);
 
+	if (keep_evalled_promises) {
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+#if R_VERSION >= R_Version(4, 6, 0)
+	if (RFn::R_GetBindingType) { // implies R runtime >= 4.6
+		const auto type = RFn::R_GetBindingType(sym, env);
+		if (type == R_BindingTypeDelayed) {
+			RK_DEBUG(RBACKEND, DL_TRACE, "temporarily resolving delayed expression");
+			return RFn::Rf_eval(RFn::R_DelayedBindingExpression(sym, env), RFn::R_DelayedBindingEnvironment(sym, env));
+		} else if (type == R_BindingTypeMissing) {
+			return ROb(R_NilValue);
+		} else if (type == R_BindingTypeUnbound) {
+			return ROb(R_NilValue);
+		}
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+#endif
+	if (!RFn::PRCODE) {
+		// We have neither the new nor the old promise handling functions?
+		// fall back to forcing
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+	SEXP from = RFn::Rf_findVar(sym, env);
 	SEXP ret = from;
 	if (RFn::TYPEOF(from) == PROMSXP) {
-		if (keep_evalled_promises) {
-			return RFn::Rf_eval(ret, env);
-		}
 		ret = RFn::PRVALUE(from);
 		if (ret == ROb(R_UnboundValue)) {
 			RK_DEBUG(RBACKEND, DL_TRACE, "temporarily resolving unbound promise");
@@ -369,7 +390,7 @@ void RKStructureGetter::getStructureWorker(SEXP val, const QString &name, int ad
 			for (int i = 0; i < childcount; ++i) {
 				SEXP current_childname = RFn::Rf_install(RFn::R_CHAR(RFn::STRING_ELT(childnames_s, i))); // ??? Why does simply using RFn::STRING_ELT(childnames_i, i) crash?
 				RFn::Rf_protect(current_childname);
-				SEXP child = resolvePromise(RFn::Rf_findVar(current_childname, value), value);
+				SEXP child = peekFromEnv(current_childname, value);
 				RFn::Rf_protect(child);
 
 				getStructureSafe(child, childnames[i], 0, children[i], nesting_depth + 1);
diff --git a/rkward/rbackend/rkstructuregetter.h b/rkward/rbackend/rkstructuregetter.h
index 9b92bffa8..6dafc92a4 100644
--- a/rkward/rbackend/rkstructuregetter.h
+++ b/rkward/rbackend/rkstructuregetter.h
@@ -36,7 +36,7 @@ class RKStructureGetter {
 	/** needed to wrap things inside an R_ToplevelExec */
 	static void getStructureWrapper(GetStructureWorkerArgs *data);
 	void getStructureSafe(SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth);
-	SEXP resolvePromise(SEXP from, SEXP env);
+	SEXP peekFromEnv(SEXP symb, SEXP env);
 
 	SEXP prefetch_fun(const char *name, bool from_base = true);
 


More information about the rkward-tracker mailing list