diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
index 647e0140bbf6faecbe62e14a6d897253acc45e6b..b6cedaff81192d02a1c9ee9699272d3944f5bfca 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
@@ -224,7 +224,7 @@ public class CallAndExternalFunctions {
     public abstract static class DotCall extends LookupAdapter {
 
         @Child CallRFFI.InvokeCallNode callRFFINode = RFFIFactory.getCallRFFI().createInvokeCallNode();
-        @Child MaterializeNode materializeNode = MaterializeNode.create();
+        @Child MaterializeNode materializeNode = MaterializeNode.create(true);
 
         static {
             Casts.noCasts(DotCall.class);
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/nodes/MaterializeNode.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/nodes/MaterializeNode.java
index 67f641bca46d4247ce9de3b0bbe5433c9c90c7e9..0378a2abac4af375e37f2336495c16e9a04002e6 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/nodes/MaterializeNode.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/nodes/MaterializeNode.java
@@ -22,28 +22,100 @@
  */
 package com.oracle.truffle.r.runtime.data.nodes;
 
+import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.attributes.HasAttributesNode;
+import com.oracle.truffle.r.nodes.attributes.IterableAttributeNode;
+import com.oracle.truffle.r.nodes.attributes.SetAttributeNode;
+import com.oracle.truffle.r.runtime.data.RAttributable;
+import com.oracle.truffle.r.runtime.data.RAttributesLayout.RAttribute;
+import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.model.RAbstractContainer;
 
 public abstract class MaterializeNode extends Node {
 
     protected static final int LIMIT = 10;
 
+    @Child private HasAttributesNode hasAttributes;
+    @Child private IterableAttributeNode attributesIt;
+    @Child private SetAttributeNode setAttributeNode;
+
+    @Child private MaterializeNode recursive;
+    @Child private MaterializeNode recursiveAttr;
+
+    private final boolean deep;
+
+    protected MaterializeNode(boolean deep) {
+        this.deep = deep;
+        if (deep) {
+            CompilerAsserts.neverPartOfCompilation();
+            hasAttributes = insert(HasAttributesNode.create());
+        }
+    }
+
     public abstract Object execute(VirtualFrame frame, Object arg);
 
+    @Specialization
+    protected RList doList(VirtualFrame frame, RList vec) {
+        RList materialized = materializeContents(frame, vec);
+        materializeAttributes(frame, materialized);
+        return materialized;
+    }
+
+    private RList materializeContents(VirtualFrame frame, RList list) {
+        boolean changed = false;
+        RList materializedContents = null;
+        for (int i = 0; i < list.getLength(); i++) {
+            if (recursive == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                recursive = insert(MaterializeNode.create(deep));
+            }
+            Object element = list.getDataAt(i);
+            Object materializedElem = recursive.execute(frame, element);
+            if (materializedElem != element) {
+                materializedContents = (RList) list.copy();
+                changed = true;
+            }
+            if (changed && materializedElem != element) {
+                materializedContents.setDataAt(i, materializedElem);
+            }
+        }
+        if (changed) {
+            return materializedContents;
+        }
+        return list;
+    }
+
     @Specialization(limit = "LIMIT", guards = {"vec.getClass() == cachedClass"})
-    protected RAbstractContainer doAbstractContainerCached(RAbstractContainer vec,
+    protected RAttributable doAbstractContainerCached(VirtualFrame frame, RAttributable vec,
                     @SuppressWarnings("unused") @Cached("vec.getClass()") Class<?> cachedClass) {
-        return vec.materialize();
+        if (vec instanceof RList) {
+            return doList(frame, (RList) vec);
+        } else if (vec instanceof RAbstractContainer) {
+            RAbstractContainer materialized = ((RAbstractContainer) vec).materialize();
+            materializeAttributes(frame, materialized);
+            return materialized;
+        }
+        materializeAttributes(frame, vec);
+        return vec;
     }
 
     @Specialization(replaces = "doAbstractContainerCached")
-    protected RAbstractContainer doAbstractContainer(RAbstractContainer vec) {
-        return vec.materialize();
+    protected RAttributable doAbstractContainer(VirtualFrame frame, RAttributable vec) {
+        if (vec instanceof RList) {
+            return doList(frame, (RList) vec);
+        } else if (vec instanceof RAbstractContainer) {
+            RAbstractContainer materialized = ((RAbstractContainer) vec).materialize();
+            materializeAttributes(frame, materialized);
+            return materialized;
+        }
+        materializeAttributes(frame, vec);
+        return vec;
     }
 
     @Fallback
@@ -51,8 +123,30 @@ public abstract class MaterializeNode extends Node {
         return o;
     }
 
-    public static MaterializeNode create() {
-        return MaterializeNodeGen.create();
+    private void materializeAttributes(VirtualFrame frame, RAttributable materialized) {
+        // TODO we could further optimize by first checking for fixed/special attributes
+        if (deep && hasAttributes.execute(materialized)) {
+            if (attributesIt == null) {
+                assert recursiveAttr == null;
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                attributesIt = insert(IterableAttributeNode.create());
+                recursiveAttr = insert(MaterializeNode.create(deep));
+            }
+            for (RAttribute attr : attributesIt.execute(materialized)) {
+                Object materializedAttr = recursiveAttr.execute(frame, attr.getValue());
+                if (materializedAttr != attr.getValue()) {
+                    if (setAttributeNode == null) {
+                        CompilerDirectives.transferToInterpreterAndInvalidate();
+                        setAttributeNode = insert(SetAttributeNode.create());
+                    }
+                    setAttributeNode.execute(materialized, attr.getName(), materializedAttr);
+                }
+            }
+        }
+    }
+
+    public static MaterializeNode create(boolean deep) {
+        return MaterializeNodeGen.create(deep);
     }
 
 }