From 607f4108bd9393ef066ccdfc327b659d6b3a0824 Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Fri, 10 Nov 2017 18:11:33 +0100
Subject: [PATCH] Support S4 objects with .Data or .xData in subscribe
 operations

---
 .../vector/CachedExtractVectorNode.java       | 15 ++++-
 .../vector/CachedReplaceVectorNode.java       | 17 +++++-
 .../nodes/access/vector/CachedVectorNode.java |  1 +
 .../access/vector/ExtractS4ObjectNode.java    | 61 +++++++++++++++++++
 .../access/vector/ReplaceS4ObjectNode.java    | 53 ++++++++++++++++
 .../access/vector/ReplaceVectorNode.java      |  2 +-
 .../access/vector/WriteIndexedVectorNode.java |  2 +-
 .../com/oracle/truffle/r/runtime/RError.java  |  2 +
 .../truffle/r/test/ExpectedTestOutput.test    | 43 +++++++++++++
 .../com/oracle/truffle/r/test/S4/TestS4.java  |  8 +++
 10 files changed, 200 insertions(+), 4 deletions(-)
 create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java
 create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java

diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
index f6e1fdd15a..903947939f 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
@@ -53,6 +53,7 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RLogical;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RS4Object;
 import com.oracle.truffle.r.runtime.data.RString;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.RTypedValue;
@@ -89,6 +90,8 @@ final class CachedExtractVectorNode extends CachedVectorNode {
 
     @Child private ExtractDimNamesNode extractDimNames;
 
+    @Child private ExtractS4ObjectNode extractS4ObjectNode;
+
     private final ConditionProfile resultHasDimensions = ConditionProfile.createBinaryProfile();
     private final ConditionProfile promiseInEnvironment = ConditionProfile.createBinaryProfile();
 
@@ -109,7 +112,7 @@ final class CachedExtractVectorNode extends CachedVectorNode {
         this.exact = logicalAsBoolean(exact, DEFAULT_EXACT);
         this.dropDimensions = logicalAsBoolean(dropDimensions, DEFAULT_DROP_DIMENSION);
         this.positionsCheckNode = new PositionsCheckNode(mode, vectorType, convertedPositions, this.exact, false, recursive);
-        if (error == null && vectorType != RType.Null && vectorType != RType.Environment) {
+        if (error == null && vectorType != RType.Null && vectorType != RType.Environment && vectorType != RType.S4Object) {
             this.writeVectorNode = WriteIndexedVectorNode.create(vectorType, convertedPositions.length, true, false, false, false);
         }
     }
@@ -148,6 +151,8 @@ final class CachedExtractVectorNode extends CachedVectorNode {
                  * later.
                  */
                 return doEnvironment((REnvironment) castVector, positions);
+            case S4Object:
+                return doS4Object((RS4Object) castVector, positions);
             case Integer:
                 vector = (RAbstractContainer) castVector;
                 break;
@@ -268,6 +273,14 @@ final class CachedExtractVectorNode extends CachedVectorNode {
         throw error(RError.Message.WRONG_ARGS_SUBSET_ENV);
     }
 
+    private Object doS4Object(RS4Object object, Object[] positions) {
+        if (extractS4ObjectNode == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            extractS4ObjectNode = insert(new ExtractS4ObjectNode(mode, exact, dropDimensions));
+        }
+        return extractS4ObjectNode.execute(object, positions);
+    }
+
     private boolean isMissingSingleDimension() {
         return numberOfDimensions == 1 && positionsCheckNode.isMissing();
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
index 73b47fda38..7158f0f986 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
@@ -57,6 +57,7 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RPairList;
+import com.oracle.truffle.r.runtime.data.RS4Object;
 import com.oracle.truffle.r.runtime.data.RScalarVector;
 import com.oracle.truffle.r.runtime.data.RShareable;
 import com.oracle.truffle.r.runtime.data.RStringVector;
@@ -94,6 +95,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
     private final boolean updatePositionNames;
 
     private final boolean isValueGt1;
+    private final boolean ignoreRecursive;
 
     @Child private WriteIndexedVectorNode writeVectorNode;
     @Child private PositionsCheckNode positionsCheckNode;
@@ -102,7 +104,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
     @Child private DeleteElementsNode deleteElementsNode;
     @Child private SetNamesAttributeNode setNamesNode;
 
-    CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive, boolean isValueGt1) {
+    CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive, boolean ignoreRecursive, boolean isValueGt1) {
         super(mode, vector, positions, recursive);
 
         if (numberOfDimensions == 1 && positions[0] instanceof String || positions[0] instanceof RAbstractStringVector) {
@@ -111,6 +113,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
             this.updatePositionNames = false;
         }
 
+        this.ignoreRecursive = ignoreRecursive;
         this.vectorClass = vector.getClass();
         this.valueClass = valueClass;
         this.valueType = valueType;
@@ -199,6 +202,8 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
                 break;
             case Environment:
                 return doEnvironment((REnvironment) castVector, positions, castValue);
+            case S4Object:
+                return doS4Object((RS4Object) castVector, positions, castValue);
             case Language:
                 repType = RContext.getRRuntimeASTAccess().getRepType((RLanguage) castVector);
                 vector = RContext.getRRuntimeASTAccess().asList((RLanguage) castVector);
@@ -483,6 +488,16 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
         return env;
     }
 
+    @Child private ReplaceS4ObjectNode replaceS4ObjectNode;
+
+    private Object doS4Object(RS4Object obj, Object[] positions, Object originalValues) {
+        if (replaceS4ObjectNode == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            replaceS4ObjectNode = insert(new ReplaceS4ObjectNode(mode, ignoreRecursive));
+        }
+        return replaceS4ObjectNode.execute(obj, positions, originalValues);
+    }
+
     @NodeInfo(cost = NONE)
     public abstract static class ValueProfileNode extends Node {
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
index bc1808ef44..595c1452ef 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
@@ -146,6 +146,7 @@ abstract class CachedVectorNode extends RBaseNode {
             case PairList:
             case Environment:
             case Expression:
+            case S4Object:
                 return true;
             default:
                 return false;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java
new file mode 100644
index 0000000000..70df313158
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.access.vector;
+
+import static com.oracle.truffle.r.runtime.RError.Message.OP_NOT_DEFINED_FOR_S4_CLASS;
+
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlot;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RS4Object;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
+
+public class ExtractS4ObjectNode extends Node {
+    @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment);
+    @Child private ExtractVectorNode extract;
+    private final boolean exact;
+    private final boolean dropDimensions;
+
+    public ExtractS4ObjectNode(ElementAccessMode accessMode, boolean exact, boolean dropDimensions) {
+        this.extract = ExtractVectorNode.create(accessMode, true);
+        this.exact = exact;
+        this.dropDimensions = dropDimensions;
+    }
+
+    public Object execute(RS4Object obj, Object[] positions) {
+        RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj);
+        if (dataSlot == RNull.instance) {
+            throw RError.error(RError.SHOW_CALLER, OP_NOT_DEFINED_FOR_S4_CLASS, "$");
+        }
+        return extract.execute(dataSlot, positions, createLogical(exact), createLogical(dropDimensions));
+    }
+
+    private static RAbstractLogicalVector createLogical(boolean b) {
+        return RDataFactory.createLogicalVectorFromScalar(b);
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java
new file mode 100644
index 0000000000..afc04f304e
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.access.vector;
+
+import static com.oracle.truffle.r.runtime.RError.Message.NO_METHOD_ASSIGNING_SUBSET_S4;
+
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlot;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RS4Object;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+
+public class ReplaceS4ObjectNode extends Node {
+    @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment);
+    @Child private ReplaceVectorNode replaceVectorNode;
+
+    public ReplaceS4ObjectNode(ElementAccessMode mode, boolean ignoreRecursive) {
+        replaceVectorNode = ReplaceVectorNode.create(mode, ignoreRecursive);
+    }
+
+    public Object execute(RS4Object obj, Object[] positions, Object values) {
+        RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj);
+        if (dataSlot == RNull.instance) {
+            throw RError.error(RError.SHOW_CALLER, NO_METHOD_ASSIGNING_SUBSET_S4);
+        }
+        // No need to update the data slot, the value is env and they have reference semantics.
+        replaceVectorNode.execute(dataSlot, positions, values);
+        return obj;
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
index cef08a1982..f295281ded 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
@@ -189,7 +189,7 @@ public abstract class ReplaceVectorNode extends RBaseNode {
 
     protected static CachedReplaceVectorNode createDefaultCached(ReplaceVectorNode node, Object vector, Object[] positions, Object value) {
         return new CachedReplaceVectorNode(node.mode, (RTypedValue) vector, positions, value.getClass(), RRuntime.isForeignObject(value) ? RType.TruffleObject : ((RTypedValue) value).getRType(), true,
-                        node.recursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value));
+                        node.recursive, node.ignoreRecursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value));
     }
 
     public ElementAccessMode getMode() {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
index ab744903fc..aecab8b403 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
@@ -373,7 +373,7 @@ abstract class WriteIndexedVectorNode extends Node {
             case List:
                 return new WriteListAction(setListElementAsObject, isReplace);
             default:
-                throw RInternalError.shouldNotReachHere();
+                throw RInternalError.shouldNotReachHere("WriteIndexedScalarNode for " + type);
         }
     }
 
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
index b8abebdd2f..1253fd0cf7 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
@@ -635,6 +635,7 @@ public final class RError extends RuntimeException implements TruffleException {
         POS_NOT_ALLOWED_WITH_NUMERIC("pos argument not allowed with a numeric value"),
         OBJ_CANNOT_BE_ATTRIBUTED("external object cannot be attributed"),
         CANNOT_COERCE_EXTERNAL_OBJECT_TO_VECTOR("no method for coercing this external object to a %s"),
+        NO_METHOD_ASSIGNING_SUBSET_S4("no method for assigning subsets of this S4 class"),
         CANNOT_COERCE_S4_TO_VECTOR("no method for coercing this S4 class to a vector"),
         // the following list is incomplete (but like GNU-R)
         INVALID_FORMAT_DOUBLE("invalid format '%s'; use format %%f, %%e, %%g or %%a for numeric objects"),
@@ -723,6 +724,7 @@ public final class RError extends RuntimeException implements TruffleException {
         RNG_SYMBOL("%s not found in user rng library"),
         CUMMAX_UNDEFINED_FOR_COMPLEX("'cummax' not defined for complex numbers"),
         CUMMIN_UNDEFINED_FOR_COMPLEX("'cummin' not defined for complex numbers"),
+        OP_NOT_DEFINED_FOR_S4_CLASS("%s operator not defined for this S4 class"),
         NMAX_LESS_THAN_ONE("'nmax' must be positive"),
         CHAR_VEC_ARGUMENT("a character vector argument expected"),
         QUOTE_G_ONE("only the first character of 'quote' will be used"),
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index ed3a0521e0..53c9acfb6a 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -604,6 +604,49 @@ Error in validObject(.Object) : invalid class “SingleInt” object: FALSE
 #{ setClass('WrappedIntVec', representation(n = 'numeric')); x0 <- new('WrappedIntVec', n = 1); x1 <- x0; x1@n <- 2; x0@n == x1@n }
 [1] FALSE
 
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); }
+[[1]]
+An object of class "TestS4CornerCases"
+Slot "fld":
+[1] "xyz"
+
+
+[[2]]
+[[2]]$fld2
+[1] "value"
+
+
+[[3]]
+[1] "value"
+
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); }
+[[1]]
+An object of class "TestS4CornerCases"
+Slot "fld":
+[1] "xyz"
+
+
+[[2]]
+[[2]]$fld2
+[1] "value"
+
+
+[[3]]
+[1] "value"
+
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; }
+Error in `$<-`(`*tmp*`, fld2, value = "value") :
+  no method for assigning subsets of this S4 class
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; }
+Error in obj$fld2 : $ operator not defined for this S4 class
+
 ##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
 # { x<-42; attr(x, "foo")<-7; x@foo }
 Error: trying to get slot "foo" from an object of a basic class ("numeric") with no slots
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
index e054c71b60..ea788cf637 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
@@ -187,4 +187,12 @@ public class TestS4 extends TestRBase {
         assertEval("makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get0') else print('set0'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1; makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get1') else print('set1'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1");
         assertEval("makeActiveBinding('var_a', function(x) { if(missing(x)) { print('get'); return(123) } else { print('set'); return(x) } }, .GlobalEnv); inherits(var_a, 'numeric')");
     }
+
+    @Test
+    public void testRegularFieldAssign() {
+        assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; }");
+        assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; }");
+        assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); }");
+        assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); }");
+    }
 }
-- 
GitLab