From c947cb989459c167dc82f1ea7f03f28da59777ca Mon Sep 17 00:00:00 2001 From: Florian Angerer <florian.angerer@oracle.com> Date: Tue, 30 May 2017 18:06:38 +0200 Subject: [PATCH] Keeping source of installed packages. Deserializing srcref attributes. --- .../r/nodes/builtin/tools/R/tools_overrides.R | 145 +++++- .../oracle/truffle/r/runtime/RSerialize.java | 475 +++++++++--------- 2 files changed, 391 insertions(+), 229 deletions(-) diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/tools/R/tools_overrides.R b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/tools/R/tools_overrides.R index d7aedd7b69..9eee831d8b 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/tools/R/tools_overrides.R +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/tools/R/tools_overrides.R @@ -51,9 +51,152 @@ makeLazyLoading <- else { code2LazyLoadDB(package, lib.loc = lib.loc, keep.source = keep.source, compress = compress) - #file.copy(loaderFile, codeFile, TRUE) + file.copy(loaderFile, codeFile, TRUE) } invisible() } + +.install_package_code_files <- +function(dir, outDir) +{ + if(!dir.exists(dir)) + stop(gettextf("directory '%s' does not exist", dir), + domain = NA) + dir <- file_path_as_absolute(dir) + + ## Attempt to set the LC_COLLATE locale to 'C' to turn off locale + ## specific sorting. + curLocale <- Sys.getlocale("LC_COLLATE") + on.exit(Sys.setlocale("LC_COLLATE", curLocale), add = TRUE) + ## (Guaranteed to work as per the Sys.setlocale() docs.) + lccollate <- "C" + if(Sys.setlocale("LC_COLLATE", lccollate) != lccollate) { + ## <NOTE> + ## I don't think we can give an error here. + ## It may be the case that Sys.setlocale() fails because the "OS + ## reports request cannot be honored" (src/main/platform.c), in + ## which case we should still proceed ... + warning("cannot turn off locale-specific sorting via LC_COLLATE") + ## </NOTE> + } + + ## We definitely need a valid DESCRIPTION file. + db <- .read_description(file.path(dir, "DESCRIPTION")) + + codeDir <- file.path(dir, "R") + if(!dir.exists(codeDir)) return(invisible()) + + codeFiles <- list_files_with_type(codeDir, "code", full.names = FALSE) + + collationField <- + c(paste("Collate", .OStype(), sep = "."), "Collate") + if(any(i <- collationField %in% names(db))) { + collationField <- collationField[i][1L] + codeFilesInCspec <- .read_collate_field(db[collationField]) + ## Duplicated entries in the collation spec? + badFiles <- + unique(codeFilesInCspec[duplicated(codeFilesInCspec)]) + if(length(badFiles)) { + out <- gettextf("\nduplicated files in '%s' field:", + collationField) + out <- paste(out, + paste(" ", badFiles, collapse = "\n"), + sep = "\n") + stop(out, domain = NA) + } + ## See which files are listed in the collation spec but don't + ## exist. + badFiles <- setdiff(codeFilesInCspec, codeFiles) + if(length(badFiles)) { + out <- gettextf("\nfiles in '%s' field missing from '%s':", + collationField, + codeDir) + out <- paste(out, + paste(" ", badFiles, collapse = "\n"), + sep = "\n") + stop(out, domain = NA) + } + ## See which files exist but are missing from the collation + ## spec. Note that we do not want the collation spec to use + ## only a subset of the available code files. + badFiles <- setdiff(codeFiles, codeFilesInCspec) + if(length(badFiles)) { + out <- gettextf("\nfiles in '%s' missing from '%s' field:", + codeDir, + collationField) + out <- paste(out, + paste(" ", badFiles, collapse = "\n"), + sep = "\n") + stop(out, domain = NA) + } + ## Everything's groovy ... + codeFiles <- codeFilesInCspec + } + + codeFiles <- file.path(codeDir, codeFiles) + + if(!dir.exists(outDir) && !dir.create(outDir)) + stop(gettextf("cannot open directory '%s'", outDir), + domain = NA) + outCodeDir <- file.path(outDir, "R") + if(!dir.exists(outCodeDir) && !dir.create(outCodeDir)) + stop(gettextf("cannot open directory '%s'", outCodeDir), + domain = NA) + outFile <- file.path(outCodeDir, db["Package"]) + if(!file.create(outFile)) + stop(gettextf("unable to create '%s'", outFile), domain = NA) + writeLines(paste0(".packageName <- \"", db["Package"], "\""), + outFile) + enc <- as.vector(db["Encoding"]) + need_enc <- !is.na(enc) # Encoding was specified + ## assume that if locale is 'C' we can used 8-bit encodings unchanged. + if(need_enc && !(Sys.getlocale("LC_CTYPE") %in% c("C", "POSIX"))) { + con <- file(outFile, "a") + on.exit(close(con)) # Windows does not like files left open + for(f in codeFiles) { + tmp <- iconv(readLines(f, warn = FALSE), from = enc, to = "") + if(length(bad <- which(is.na(tmp)))) { + warning(sprintf(ngettext(length(bad), + "unable to re-encode %s line %s", + "unable to re-encode %s lines %s"), + sQuote(basename(f)), + paste(bad, collapse = ", ")), + domain = NA, call. = FALSE) + tmp <- iconv(readLines(f, warn = FALSE), from = enc, to = "", + sub = "byte") + } + + # FastR extension: also copy original source file + singleOutFile <- file.path(outCodeDir, basename(f)) + writeLines(tmp, file(singleOutFile)) + + writeLines(paste0("#line 1 \"", f, "\""), con) + writeLines(tmp, con) + } + close(con); on.exit() + } else { + ## <NOTE> + ## It may be safer to do + ## writeLines(sapply(codeFiles, readLines), outFile) + ## instead, but this would be much slower ... + ## use fast version of file.append that ensures LF between files + + # FastR extension: also copy original source file + singleOutFiles <- file.path(outCodeDir, basename(codeFiles)) + file.copy(codeFiles, singleOutFiles) + + if(!all(.file_append_ensuring_LFs(outFile, singleOutFiles))) + stop("unable to write code files") + ## </NOTE> + } + ## A syntax check here, so that we do not install a broken package. + ## FIXME: this is only needed if we don't lazy load, as the lazy loader + ## would detect the error. + op <- options(showErrorCalls=FALSE) + on.exit(options(op)) + parse(outFile) + invisible() +} + }), asNamespace("tools")) diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java index b9f432b18f..494ff5bb7b 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java @@ -1434,269 +1434,272 @@ public class RSerialize { outRefIndex(refIndex); } else if (type == SEXPTYPE.SYMSXP) { writeSymbol((RSymbol) obj); - } else if (type == SEXPTYPE.ENVSXP) { - REnvironment env = (REnvironment) obj; - addReadRef(obj); - String name = null; - if ((name = env.isPackageEnv()) != null) { - RError.warning(RError.SHOW_CALLER2, RError.Message.PACKAGE_AVAILABLE, name); - stream.writeInt(SEXPTYPE.PACKAGESXP.code); - stream.writeString(name); - } else if (env.isNamespaceEnv()) { - stream.writeInt(SEXPTYPE.NAMESPACESXP.code); - RStringVector nameSpaceEnvSpec = env.getNamespaceSpec(); - outStringVec(nameSpaceEnvSpec, false); - } else { - stream.writeInt(SEXPTYPE.ENVSXP.code); - stream.writeInt(env.isLocked() ? 1 : 0); - writeItem(env.getParent()); - /* - * TODO To be truly compatible with GnuR we should remember whether an - * environment was created with new.env(hash=T) and output it in that form - * with the associated size. For internal FastR use it does not matter, so - * we use the "frame" form, which is a pairlist. tag is binding name, car is - * binding value - */ - String[] bindings = env.ls(true, null, false).getDataWithoutCopying(); - for (String binding : bindings) { - Object value = getValueIgnoreActiveBinding(env.getFrame(), binding); - writePairListEntry(binding, value); - } - terminatePairList(); - writeItem(RNull.instance); // hashtab - DynamicObject attributes = env.getAttributes(); - writeAttributes(attributes, getSourceSection(obj)); - if (attributes == null) { - writeItem(RNull.instance); - } - } } else { - // flags - DynamicObject attributes = null; - SourceSection ss = getSourceSection(obj); - if (obj instanceof RAttributable) { - RAttributable rattr = (RAttributable) obj; - attributes = rattr.getAttributes(); - if (attributes != null && attributes.isEmpty()) { - attributes = null; + SourceSection sourceSection = getSourceSection(obj); + if (type == SEXPTYPE.ENVSXP) { + REnvironment env = (REnvironment) obj; + addReadRef(obj); + String name = null; + if ((name = env.isPackageEnv()) != null) { + RError.warning(RError.SHOW_CALLER2, RError.Message.PACKAGE_AVAILABLE, name); + stream.writeInt(SEXPTYPE.PACKAGESXP.code); + stream.writeString(name); + } else if (env.isNamespaceEnv()) { + stream.writeInt(SEXPTYPE.NAMESPACESXP.code); + RStringVector nameSpaceEnvSpec = env.getNamespaceSpec(); + outStringVec(nameSpaceEnvSpec, false); + } else { + stream.writeInt(SEXPTYPE.ENVSXP.code); + stream.writeInt(env.isLocked() ? 1 : 0); + writeItem(env.getParent()); + /* + * TODO To be truly compatible with GnuR we should remember whether an + * environment was created with new.env(hash=T) and output it in that + * form with the associated size. For internal FastR use it does not + * matter, so we use the "frame" form, which is a pairlist. tag is + * binding name, car is binding value + */ + String[] bindings = env.ls(true, null, false).getDataWithoutCopying(); + for (String binding : bindings) { + Object value = getValueIgnoreActiveBinding(env.getFrame(), binding); + writePairListEntry(binding, value); + } + terminatePairList(); + writeItem(RNull.instance); // hashtab + DynamicObject attributes = env.getAttributes(); + writeAttributes(attributes, sourceSection); + if (attributes == null && sourceSection == null) { + writeItem(RNull.instance); + } } - } - boolean hasTag = gnuRType == SEXPTYPE.CLOSXP || gnuRType == SEXPTYPE.DOTSXP || (gnuRType == SEXPTYPE.PROMSXP && !((RPromise) obj).isEvaluated()) || - (type == SEXPTYPE.LISTSXP && !((RPairList) obj).isNullTag()); - int gpbits = getGPBits(obj); - int flags = Flags.packFlags(gnuRType, gpbits, isObject(obj), attributes != null, hasTag); - stream.writeInt(flags); - switch (type) { - case STRSXP: { - if (obj instanceof String) { - // length 1 vector - stream.writeInt(1); - writeCHARSXP((String) obj); - } else { - outStringVec((RAbstractStringVector) obj, true); + } else { + // flags + DynamicObject attributes = null; + SourceSection ss = sourceSection; + if (obj instanceof RAttributable) { + RAttributable rattr = (RAttributable) obj; + attributes = rattr.getAttributes(); + if (attributes != null && attributes.isEmpty()) { + attributes = null; } - break; } + boolean hasTag = gnuRType == SEXPTYPE.CLOSXP || gnuRType == SEXPTYPE.DOTSXP || (gnuRType == SEXPTYPE.PROMSXP && !((RPromise) obj).isEvaluated()) || + (type == SEXPTYPE.LISTSXP && !((RPairList) obj).isNullTag()); + int gpbits = getGPBits(obj); + int flags = Flags.packFlags(gnuRType, gpbits, isObject(obj), attributes != null, hasTag); + stream.writeInt(flags); + switch (type) { + case STRSXP: { + if (obj instanceof String) { + // length 1 vector + stream.writeInt(1); + writeCHARSXP((String) obj); + } else { + outStringVec((RAbstractStringVector) obj, true); + } + break; + } - case INTSXP: { - if (obj instanceof Integer) { - stream.writeInt(1); - stream.writeInt((int) obj); - } else { - RAbstractIntVector vec = (RAbstractIntVector) obj; - stream.writeInt(vec.getLength()); - for (int i = 0; i < vec.getLength(); i++) { - stream.writeInt(vec.getDataAt(i)); + case INTSXP: { + if (obj instanceof Integer) { + stream.writeInt(1); + stream.writeInt((int) obj); + } else { + RAbstractIntVector vec = (RAbstractIntVector) obj; + stream.writeInt(vec.getLength()); + for (int i = 0; i < vec.getLength(); i++) { + stream.writeInt(vec.getDataAt(i)); + } } + break; } - break; - } - case REALSXP: { - if (obj instanceof Double) { - stream.writeInt(1); - stream.writeDouble((double) obj); - } else { - RAbstractDoubleVector vec = (RAbstractDoubleVector) obj; - stream.writeInt(vec.getLength()); - for (int i = 0; i < vec.getLength(); i++) { - stream.writeDouble(vec.getDataAt(i)); + case REALSXP: { + if (obj instanceof Double) { + stream.writeInt(1); + stream.writeDouble((double) obj); + } else { + RAbstractDoubleVector vec = (RAbstractDoubleVector) obj; + stream.writeInt(vec.getLength()); + for (int i = 0; i < vec.getLength(); i++) { + stream.writeDouble(vec.getDataAt(i)); + } } + break; } - break; - } - case LGLSXP: { - // Output as ints - if (obj instanceof Byte) { - stream.writeInt(1); - stream.writeInt(RRuntime.logical2int((byte) obj)); - } else { - RAbstractLogicalVector vec = (RAbstractLogicalVector) obj; + case LGLSXP: { + // Output as ints + if (obj instanceof Byte) { + stream.writeInt(1); + stream.writeInt(RRuntime.logical2int((byte) obj)); + } else { + RAbstractLogicalVector vec = (RAbstractLogicalVector) obj; + stream.writeInt(vec.getLength()); + for (int i = 0; i < vec.getLength(); i++) { + stream.writeInt(RRuntime.logical2int(vec.getDataAt(i))); + } + } + break; + } + + case CPLXSXP: { + RAbstractComplexVector vec = (RAbstractComplexVector) obj; stream.writeInt(vec.getLength()); for (int i = 0; i < vec.getLength(); i++) { - stream.writeInt(RRuntime.logical2int(vec.getDataAt(i))); + RComplex val = vec.getDataAt(i); + if (RRuntime.isNA(val)) { + stream.writeDouble(RRuntime.DOUBLE_NA); + stream.writeDouble(RRuntime.DOUBLE_NA); + } else { + stream.writeDouble(val.getRealPart()); + stream.writeDouble(val.getImaginaryPart()); + } } + break; } - break; - } - case CPLXSXP: { - RAbstractComplexVector vec = (RAbstractComplexVector) obj; - stream.writeInt(vec.getLength()); - for (int i = 0; i < vec.getLength(); i++) { - RComplex val = vec.getDataAt(i); - if (RRuntime.isNA(val)) { - stream.writeDouble(RRuntime.DOUBLE_NA); - stream.writeDouble(RRuntime.DOUBLE_NA); + case EXPRSXP: + case VECSXP: { + RAbstractVector list; + if (type == SEXPTYPE.EXPRSXP) { + list = (RExpression) obj; } else { - stream.writeDouble(val.getRealPart()); - stream.writeDouble(val.getImaginaryPart()); + list = (RList) obj; } + stream.writeInt(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + Object listObj = list.getDataAtAsObject(i); + writeItem(listObj); + } + break; } - break; - } - case EXPRSXP: - case VECSXP: { - RAbstractVector list; - if (type == SEXPTYPE.EXPRSXP) { - list = (RExpression) obj; - } else { - list = (RList) obj; - } - stream.writeInt(list.getLength()); - for (int i = 0; i < list.getLength(); i++) { - Object listObj = list.getDataAtAsObject(i); - writeItem(listObj); + case RAWSXP: { + RRawVector raw = (RRawVector) obj; + byte[] data = raw.getDataWithoutCopying(); + stream.writeInt(data.length); + stream.writeRaw(data); + break; } - break; - } - case RAWSXP: { - RRawVector raw = (RRawVector) obj; - byte[] data = raw.getDataWithoutCopying(); - stream.writeInt(data.length); - stream.writeRaw(data); - break; - } - - case EXTPTRSXP: { - addReadRef(obj); - RExternalPtr xptr = (RExternalPtr) obj; - writeItem(xptr.getProt()); - writeItem(xptr.getTag()); - break; - } - - case S4SXP: { - break; - } - - /* - * The objects that GnuR represents as a pairlist. To avoid stack overflow, - * these utilize manual tail recursion on the cdr of the - * pairlist.closePairList - */ - - case FUNSXP: - case PROMSXP: - case LANGSXP: - case LISTSXP: - case DOTSXP: { - if (type == SEXPTYPE.FUNSXP && gnuRType == SEXPTYPE.BUILTINSXP) { - // special case - RFunction fun = (RFunction) obj; - String name = fun.getRBuiltin().getName(); - stream.writeString(name); + case EXTPTRSXP: { + addReadRef(obj); + RExternalPtr xptr = (RExternalPtr) obj; + writeItem(xptr.getProt()); + writeItem(xptr.getTag()); break; } - if (type == SEXPTYPE.FUNSXP) { - RFunction fun = (RFunction) obj; - RSyntaxFunction body = (RSyntaxFunction) fun.getRootNode(); - ss = body.getLazySourceSection(); - } - tailCall = true; - // attributes written first to avoid recursion on cdr - writeAttributes(attributes, ss); - if (attributes != null) { - attributes = null; - } - if (ss != null) { - ss = null; + case S4SXP: { + break; } - switch (type) { - case FUNSXP: { + /* + * The objects that GnuR represents as a pairlist. To avoid stack + * overflow, these utilize manual tail recursion on the cdr of the + * pairlist.closePairList + */ + + case FUNSXP: + case PROMSXP: + case LANGSXP: + case LISTSXP: + case DOTSXP: { + if (type == SEXPTYPE.FUNSXP && gnuRType == SEXPTYPE.BUILTINSXP) { + // special case RFunction fun = (RFunction) obj; - RPairList pl = (RPairList) serializeLanguageObject(state, fun); - assert pl != null; - state.convertUnboundValues(pl); - if (FastROptions.debugMatches("printWclosure")) { - Debug.printClosure(pl); - } - writeItem(pl.getTag()); - writeItem(pl.car()); - obj = pl.cdr(); + String name = fun.getRBuiltin().getName(); + stream.writeString(name); break; } + if (type == SEXPTYPE.FUNSXP) { + RFunction fun = (RFunction) obj; + RSyntaxFunction body = (RSyntaxFunction) fun.getRootNode(); + ss = body.getLazySourceSection(); + } + tailCall = true; - case PROMSXP: { - RPairList pl = (RPairList) serializeLanguageObject(state, obj); - assert pl != null; - state.convertUnboundValues(pl); - if (pl.getTag() != RNull.instance) { - writeItem(pl.getTag()); - } - writeItem(pl.car()); - obj = pl.cdr(); - break; + // attributes written first to avoid recursion on cdr + writeAttributes(attributes, ss); + if (attributes != null) { + attributes = null; + } + if (ss != null) { + ss = null; } - case LISTSXP: { - RPairList pl = (RPairList) obj; - if (!pl.isNullTag()) { + switch (type) { + case FUNSXP: { + RFunction fun = (RFunction) obj; + RPairList pl = (RPairList) serializeLanguageObject(state, fun); + assert pl != null; + state.convertUnboundValues(pl); + if (FastROptions.debugMatches("printWclosure")) { + Debug.printClosure(pl); + } writeItem(pl.getTag()); + writeItem(pl.car()); + obj = pl.cdr(); + break; } - writeItem(pl.car()); - obj = pl.cdr(); - break; - } - case LANGSXP: { - RPairList pl = (RPairList) serializeLanguageObject(state, obj); - state.convertUnboundValues(pl); - writeItem(pl.car()); - obj = pl.cdr(); - break; - } + case PROMSXP: { + RPairList pl = (RPairList) serializeLanguageObject(state, obj); + assert pl != null; + state.convertUnboundValues(pl); + if (pl.getTag() != RNull.instance) { + writeItem(pl.getTag()); + } + writeItem(pl.car()); + obj = pl.cdr(); + break; + } - case DOTSXP: { - // This in GnuR is a pairlist - RArgsValuesAndNames rvn = (RArgsValuesAndNames) obj; - Object list = RNull.instance; - for (int i = rvn.getLength() - 1; i >= 0; i--) { - String name = rvn.getSignature().getName(i); - list = RDataFactory.createPairList(rvn.getArgument(i), list, name == null ? RNull.instance : RDataFactory.createSymbolInterned(name)); + case LISTSXP: { + RPairList pl = (RPairList) obj; + if (!pl.isNullTag()) { + writeItem(pl.getTag()); + } + writeItem(pl.car()); + obj = pl.cdr(); + break; } - RPairList pl = (RPairList) list; - if (!pl.isNullTag()) { - writeItem(pl.getTag()); + + case LANGSXP: { + RPairList pl = (RPairList) serializeLanguageObject(state, obj); + state.convertUnboundValues(pl); + writeItem(pl.car()); + obj = pl.cdr(); + break; + } + + case DOTSXP: { + // This in GnuR is a pairlist + RArgsValuesAndNames rvn = (RArgsValuesAndNames) obj; + Object list = RNull.instance; + for (int i = rvn.getLength() - 1; i >= 0; i--) { + String name = rvn.getSignature().getName(i); + list = RDataFactory.createPairList(rvn.getArgument(i), list, name == null ? RNull.instance : RDataFactory.createSymbolInterned(name)); + } + RPairList pl = (RPairList) list; + if (!pl.isNullTag()) { + writeItem(pl.getTag()); + } + writeItem(pl.car()); + obj = pl.cdr(); + break; } - writeItem(pl.car()); - obj = pl.cdr(); - break; } + break; } - break; + + default: + throw RInternalError.unimplemented(type.name()); } - default: - throw RInternalError.unimplemented(type.name()); + writeAttributes(attributes, ss); } - - writeAttributes(attributes, ss); } } while (tailCall); } @@ -1778,6 +1781,13 @@ public class RSerialize { } private void writeAttributes(DynamicObject attributes, SourceSection ss) throws IOException { + if (ss != null && ss != RSyntaxNode.LAZY_DEPARSE) { + String path = ss.getSource().getURI().getPath(); + REnvironment createSrcfile = RSrcref.createSrcfile(path); + Object createLloc = RSrcref.createLloc(ss); + writePairListEntry(RRuntime.R_SRCREF, createLloc); + writePairListEntry(RRuntime.R_SRCFILE, createSrcfile); + } if (attributes != null) { // have to convert to GnuR pairlist Iterator<RAttributesLayout.RAttribute> iter = RAttributesLayout.asIterable(attributes).iterator(); @@ -1789,14 +1799,7 @@ public class RSerialize { writePairListEntry(attr.getName(), attr.getValue()); } } - if (ss != null && ss != RSyntaxNode.LAZY_DEPARSE) { - String path = ss.getSource().getURI().getPath(); - REnvironment createSrcfile = RSrcref.createSrcfile(path); - RIntVector createLloc = RSrcref.createLloc(ss, createSrcfile); - writePairListEntry(RRuntime.R_SRCREF, createLloc); - writePairListEntry(RRuntime.R_SRCFILE, createSrcfile); - } - if (attributes != null || ss != null) { + if (attributes != null || ss != null && ss != RSyntaxNode.LAZY_DEPARSE) { terminatePairList(); } } @@ -2415,6 +2418,21 @@ public class RSerialize { return state.closePairList(); } + private static Object extractFromList(Object tag, SEXPTYPE expectedType) { + SEXPTYPE type = SEXPTYPE.typeForClass(tag.getClass()); + if (type == expectedType) { + return tag; + } else if (type == SEXPTYPE.LISTSXP) { + for (RPairList item : (RPairList) tag) { + Object data = item.car(); + if (SEXPTYPE.typeForClass(data.getClass()) == expectedType) { + return data; + } + } + } + return null; + } + /** * A collection of static functions that will transform a pairlist into an AST using the * {@link RCodeBuilder}. @@ -2422,9 +2440,10 @@ public class RSerialize { private static final class PairlistDeserializer { public static RFunction processFunction(Object car, Object cdr, Object tag, String functionName, String packageName) { - // car == arguments, cdr == body, tag == environment + // car == arguments, cdr == body, tag == PairList(attributes, environment) REnvironment environment = (REnvironment) tag; + MaterializedFrame enclosingFrame = environment.getFrame(); RootCallTarget callTarget = RContext.getASTBuilder().rootFunction(RSyntaxNode.LAZY_DEPARSE, processArguments(car), processBody(cdr), functionName); -- GitLab