diff --git a/ChangeLog b/ChangeLog
index 836d2fbecc3305a169e3282958e4af7716167fee..6be4b5de854b714cbeae57430bf2b9c8500f2169 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,7 +4,8 @@
     attributes are now generated only for node types that have a rewrite, and
     the attribute relies on the rewrite condition to decide when the rewrite
     has reached a final result.
-    * Removed list rewrites. List rewrites were an undocumented feature that
+    * List rewrite have been deprecated in legacy rewrite mode and removed in
+    circular NTA rewrite mode. List rewrites were an undocumented feature that
     caused problems for attribute correctness by allowing nodes to change
     position in a list after the AST was constructed.
     * Removed staged rewrites. Rewrite stages were an undocumented,
diff --git a/src/jastadd/ast/Ast.ast b/src/jastadd/ast/Ast.ast
index 16c35bb5feefc8131099f16a6691d50c1f47e7ac..15f99974e82c56bedd28042cdf91907cfdf293c2 100644
--- a/src/jastadd/ast/Ast.ast
+++ b/src/jastadd/ast/Ast.ast
@@ -58,6 +58,8 @@ ClassBodyDecl ::= <Name> <FileName> <StartLine:int> <EndLine:int> <AspectName:St
 
 Rewrite ::= <FileName> <StartLine:int> <EndLine:int> <AspectName>;
 
+RewriteList : Rewrite;
+
 /** A component of an AST node type declaration. */
 abstract Component;
 
diff --git a/src/jastadd/ast/AttributeProblems.jrag b/src/jastadd/ast/AttributeProblems.jrag
index e507ab641a06100bbe73afff632a5e5fe6d57d1c..8964ee3ba245cc42287eeb4a33594fdecaee71e2 100644
--- a/src/jastadd/ast/AttributeProblems.jrag
+++ b/src/jastadd/ast/AttributeProblems.jrag
@@ -140,6 +140,16 @@ aspect AttributeProblems {
           .sourceLine(getStartLine())
           .buildError();
 
+  /**
+   * Create a new error object with relevant file name and line number.
+   */
+  syn Problem Rewrite.error(String message) =
+      Problem.builder()
+          .message(message)
+          .sourceFile(getFileName())
+          .sourceLine(getStartLine())
+          .buildError();
+
   /**
    * Create a new error object with relevant file name and line number.
    */
@@ -160,6 +170,14 @@ aspect AttributeProblems {
     return warning(String.format(messagefmt, args));
   }
 
+  /** Create a new warning with the relevant file name and line number. */
+  syn Problem Rewrite.warning(String message) =
+      Problem.builder()
+          .message(message)
+          .sourceFile(getFileName())
+          .sourceLine(getStartLine())
+          .buildWarning();
+
   /** @return Previous equation for same synthesized attribute. */
   syn SynEq SynEq.prevEq() = hostClass().lookupSynEq(signature());
 
@@ -482,4 +500,16 @@ aspect AttributeProblems {
       when root != null && root() == null
       to TypeDecl.attributeProblems()
       for hostClass();
+
+  RewriteList contributes
+      error("list rewrites are not supported when using cnta rewrites")
+      when config().rewriteCircularNTA()
+      to TypeDecl.attributeProblems()
+      for hostClass();
+
+  RewriteList contributes
+      warning("list rewrites are deprecated and will be removed in a future version of JastAdd")
+      when config().legacyRewrite()
+      to TypeDecl.attributeProblems()
+      for hostClass();
 }
diff --git a/src/jastadd/ast/Attributes.jrag b/src/jastadd/ast/Attributes.jrag
index 6d374556fee31780a89cbef856a348e8de525706..28306eaff07e2a62c57733b493e6693ea04e11dd 100644
--- a/src/jastadd/ast/Attributes.jrag
+++ b/src/jastadd/ast/Attributes.jrag
@@ -85,6 +85,45 @@ public aspect Attributes {
     aspectMap.put(name, comment);
   }
 
+  // Add attributes to AST.
+  public void Grammar.addRewriteList(
+      String className,
+      org.jastadd.jrag.AST.SimpleNode condition,
+      org.jastadd.jrag.AST.SimpleNode result,
+      String type,
+      String fileName,
+      int startLine,
+      int endLine,
+      String parentName,
+      String childName,
+      String aspectName) {
+
+    if (!config().rewriteEnabled()) {
+      error("can not use rewrites while rewrites are disabled (enable with --rewrite=regular)",
+          fileName, startLine);
+      return;
+    }
+
+    TypeDecl c = lookup(className);
+    if (c != null && c instanceof ASTDecl) {
+      RewriteList r = new RewriteList();
+      r.setFileName(fileName);
+      r.setStartLine(startLine);
+      r.setEndLine(endLine);
+      r.setCondition(condition);
+      r.setResult(result);
+      r.setReturnType(type);
+      r.setParentName(parentName);
+      r.setChildName(childName);
+      r.setAspectName(aspectName);
+      ((ASTDecl)c).addRewrite(r);
+    } else if (c != null) {
+      error("can not rewrite to non AST class '" + className + "'", fileName, startLine);
+    } else {
+      error("can not rewrite to unknown class '" + className + "'", fileName, startLine);
+    }
+  }
+
   public void Grammar.addRewrite(
       String className,
       org.jastadd.jrag.AST.SimpleNode condition,
@@ -1170,4 +1209,24 @@ public aspect Attributes {
   public void Rewrite.setReturnType(String type) {
     returnType = type;
   }
+
+  public String RewriteList.parentName;
+
+  public String RewriteList.getParentName() {
+    return parentName;
+  }
+
+  public void RewriteList.setParentName(String name) {
+    parentName = name;
+  }
+
+  public String RewriteList.childName;
+
+  public String RewriteList.getChildName() {
+    return childName;
+  }
+
+  public void RewriteList.setChildName(String name) {
+    childName = name;
+  }
 }
diff --git a/src/jastadd/ast/JaddCodeGen.jrag b/src/jastadd/ast/JaddCodeGen.jrag
index 089facb2803a3daf8a1e2224a09f400d49a0a29d..00f431c8945d031e1bddf86c2032937a15429c87 100644
--- a/src/jastadd/ast/JaddCodeGen.jrag
+++ b/src/jastadd/ast/JaddCodeGen.jrag
@@ -349,6 +349,12 @@ aspect JaddCodeGen {
     }
   }
 
+  /**
+   * @return <code>true</code> if the list$touched field is needed for this
+   * ASTDecl.
+   */
+  syn boolean ASTDecl.needsListTouched() = config().legacyRewrite();
+
   /**
    * Generate implicit aspect declarations for the List type.
    *
@@ -473,7 +479,9 @@ aspect JaddCodeGen {
     out.println(ind + " * @apilevel internal");
     out.println(ind + " */");
     out.println(ind + "public boolean " + name() + ".mayHaveRewrite() {");
-    if (hasRewrites()) {
+    if (config().legacyRewrite() && name().equals(config().listType())) {
+      out.println(ind2 + "return true;");
+    } else if (hasRewrites()) {
       out.println(ind2 + "return true;");
     } else {
       out.println(ind2 + "return false;");
@@ -524,17 +532,6 @@ aspect JaddCodeGen {
     genIncremental(out);
   }
 
-  syn boolean ASTDecl.rewriteWithNoPhaseCondition() {
-    for (int i = 0; i < getNumRewrite(); i++) {
-      if (getRewrite(i).getCondition() == null)
-        return true;
-      String condition = Unparser.unparse(getRewrite(i).getCondition());
-      if (condition.indexOf("inRewritePhase") == -1 && condition.indexOf("inExactRewritePhase") == -1)
-        return true;
-    }
-    return superClass() instanceof ASTDecl && ((ASTDecl)superClass()).rewriteWithNoPhaseCondition();
-  }
-
   public abstract void Component.jaddGen(int index, boolean publicModifier, ASTDecl decl);
 
   public void ListComponent.jaddGen(int index, boolean publicModifier, ASTDecl decl) {
diff --git a/src/jastadd/ast/Rewrites.jrag b/src/jastadd/ast/Rewrites.jrag
index 1a84c52702d97d77d3837365a9babb2818a822e3..9f70384527ef426caba3d1cfe5778f3d0de128fa 100644
--- a/src/jastadd/ast/Rewrites.jrag
+++ b/src/jastadd/ast/Rewrites.jrag
@@ -37,6 +37,9 @@ aspect Rewrites {
 
     tt.expand("ASTDecl.rewriteTo:begin", out);
 
+    if (config().legacyRewrite() && name().equals(config().listType())) {
+      tt.expand("ASTDecl.emitRewrites.touch_list", out);
+    }
     for (int i = 0; i < getNumRewrite(); i++) {
       Rewrite r = getRewrite(i);
       if (r.genRewrite(out, i)) {
@@ -87,6 +90,18 @@ aspect Rewrites {
     }
   }
 
+  public boolean RewriteList.genRewrite(PrintStream out, int index) {
+    TemplateContext tt = templateContext();
+    tt.expand("Rewrite.declaredat", out);
+    if (getCondition() != null) {
+      tt.bind("Condition", " && " + Unparser.unparse(getCondition()));
+    } else {
+      tt.bind("Condition", "");
+    }
+    tt.expand("RewriteList.genRewrite", out);
+    return false;
+  }
+
   public void Rewrite.genRewriteCondition(PrintStream out, int index) {
     TemplateContext tt = templateContext();
     tt.bind("RewriteIndex", "" + index);
@@ -114,6 +129,23 @@ aspect Rewrites {
     }
   }
 
+  public void RewriteList.genRewritesExtra(PrintStream out, int index) {
+    String ind = config().indent;
+    String ind2 = config().ind(2);
+    if (getResult() instanceof org.jastadd.jrag.AST.ASTBlock) {
+      templateContext().expand("Rewrite.javaDoc:internal", out);
+      out.println(ind + "private " + getReturnType() + " rewrite"
+          + getParentName() + "_" + getChildName() + "() {");
+      out.print(Unparser.unparse(getResult()));
+      out.println(ind + "}");
+    } else {
+      templateContext().expand("Rewrite.javaDoc:internal", out);
+      out.println(ind + "private " + getReturnType() + " rewrite" + getParentName() + "_" + getChildName() + "() {");
+      out.println(ind2 + "return " + Unparser.unparse(getResult()) + ";");
+      out.println(ind + "}");
+    }
+  }
+
   syn lazy boolean ASTDecl.hasRewrites() =
       getNumRewrite() > 0 || (superClass() != null && superClass().hasRewrites());
 
@@ -169,4 +201,6 @@ aspect Rewrites {
           "",
           "",
           new List<Annotation>());
+
+  inh TypeDecl Rewrite.hostClass();
 }
diff --git a/src/javacc/jrag/Jrag.jjt b/src/javacc/jrag/Jrag.jjt
index 7b86d44b2482c06c94089e86fd7c112e7d3655d4..9bbcd42428a3c6e644f20c5dca99ed81e48fcbde 100644
--- a/src/javacc/jrag/Jrag.jjt
+++ b/src/javacc/jrag/Jrag.jjt
@@ -1138,15 +1138,22 @@ void AspectRewrite() :
   SimpleNode eq;
   Token first, last;
 
+  Token parent = null;
+  Token child = null;
   String type;
 }
 {
   "rewrite" {cond = null; first = token;} t = <IDENTIFIER> { className = t.image; }
+  [<IDENTIFIER> parent = <IDENTIFIER> "." child = <IDENTIFIER> <LPAREN> <RPAREN>]
   <LBRACE>
     ( ["when" { first = token; } <LPAREN> cond = Expression() <RPAREN>]
       "to" type = AspectType() ( (eq = Expression() ";" { last = token; }) | eq = Block() { last = token; } )
       {
-        root.addRewrite(className, cond, eq, type, fileName, first.beginLine, last.endLine, enclosingAspect);
+        if (parent != null && child != null) {
+          root.addRewriteList(className, cond, eq, type, fileName, first.beginLine, last.endLine, parent.image, child.image, enclosingAspect);
+        } else {
+          root.addRewrite(className, cond, eq, type, fileName, first.beginLine, last.endLine, enclosingAspect);
+        }
         cond = null;
       }
     )+
diff --git a/src/template/ast/ASTNode.tt b/src/template/ast/ASTNode.tt
index ec28d309143f3faafd220f6302adf68b4def2f80..4962dba55901a99b97d03898922b74dc2c45ecd5 100644
--- a/src/template/ast/ASTNode.tt
+++ b/src/template/ast/ASTNode.tt
@@ -52,7 +52,12 @@ $if(RewriteCircularNTA)
       }
     }
 $else
+  $if(LegacyRewrite)
+    // Legacy rewrites with rewrite in list can change child position, so update may be needed.
+    if (node.childIndex >= 0 && node.childIndex < numChildren && node == children[node.childIndex]) {
+  $else
     if (node.childIndex >= 0) {
+  $endif
       return node.childIndex;
     }
     for (int i = 0; children != null && i < children.length; i++) {
diff --git a/src/template/ast/List.tt b/src/template/ast/List.tt
index c4adae5ff531c11b65701ae9e440b4bd05312fba..b253a75714e8ed3a4559e8b1fb5ee469f8efe97d 100644
--- a/src/template/ast/List.tt
+++ b/src/template/ast/List.tt
@@ -30,6 +30,10 @@
 
 List.implicitAspectDecls [[
 
+$if(#needsListTouched)
+  private boolean $List.list$$touched = true;
+$endif
+
   public $List<T> $List.add(T node) {
     $SynchBegin
 $if(DebugMode)
@@ -66,12 +70,18 @@ $endif
 
   public void $List.insertChild($ASTNode node, int i) {
     $SynchBegin
+$if(#needsListTouched)
+    list$$touched = true;
+$endif
     super.insertChild(node, i);
     $SynchEnd
   }
 
   public void $List.addChild(T node) {
     $SynchBegin
+$if(#needsListTouched)
+    list$$touched = true;
+$endif
     super.addChild(node);
     $SynchEnd
   }
@@ -79,12 +89,23 @@ $endif
   /** @apilevel low-level */
   public void $List.removeChild(int i) {
     $SynchBegin
+$if(#needsListTouched)
+    list$$touched = true;
+$endif
     super.removeChild(i);
     $SynchEnd
   }
 
   public int $List.getNumChild() {
     $SynchBegin
+$if(#needsListTouched)
+    if (list$$touched) {
+      for (int i = 0; i < getNumChildNoTransform(); i++) {
+        getChild(i);
+      }
+      list$$touched = false;
+    }
+$endif
     return getNumChildNoTransform();
     $SynchEnd
   }
diff --git a/src/template/ast/Rewrites.tt b/src/template/ast/Rewrites.tt
index 1ec844a86d55b53db68101c7cc2fd6e4e76517f2..916e3bccc5d0c55cbf417c91ebead6776e0d28b3 100644
--- a/src/template/ast/Rewrites.tt
+++ b/src/template/ast/Rewrites.tt
@@ -30,6 +30,22 @@ ASTDecl.rewriteTo:begin [[
   public $ASTNode rewriteTo() {
 ]]
 
+ASTDecl.emitRewrites.touch_list = [[
+    if (list$$touched) {
+      for(int i = 0 ; i < getNumChildNoTransform(); i++) {
+        getChild(i);
+      }
+      list$$touched = false;
+$if(RewriteCircularNTA)
+      if (state().changeInCycle()) {
+        return this;
+      }
+$else
+      return this;
+$endif
+    }
+]]
+
 ASTDecl.rewriteTo:end:ASTNode [[
 $if(LegacyRewrite)
     if (state().peek() == $StateClass.REWRITE_CHANGE) {
@@ -85,6 +101,20 @@ Rewrite.genRewrite:unconditional [[
     return rewriteRule$RewriteIndex();
 ]]
 
+RewriteList.genRewrite [[
+    if (getParent().getParent() instanceof #getParentName &&
+        ((#getParentName)getParent().getParent()).#(getChildName)ListNoTransform() == getParent()$Condition) {
+      $List list = ($List) getParent();
+      int i = list.getIndexOfChild(this);
+      $List newList = rewrite#(getParentName)_#getChildName();
+      // The first child is set by the normal rewrite loop.
+      for(int j = 1; j < newList.getNumChildNoTransform(); j++) {
+        list.insertChild(newList.getChildNoTransform(j), ++i);
+      }
+      return newList.getChildNoTransform(0);
+    }
+]]
+
 Rewrite.javaDoc:internal [[
   /**
    * #declaredat