diff --git a/src/backend/nodes/README b/src/backend/nodes/README
index 2d6a7bcf7a..b3dc9afaf7 100644
--- a/src/backend/nodes/README
+++ b/src/backend/nodes/README
@@ -6,15 +6,16 @@ Node Structures
 Introduction
 ------------
 
-The node structures are plain old C structures with the first field of
-type NodeTag.  "Inheritance" is achieved by convention: The first
-field can alternatively be of another node type.  Functions that
-manipulate node structures reside in this directory.  Some support
-functions are automatically generated by the gen_node_support.pl
-script, other functions are maintained manually.  To control the
-automatic generation of some support functions, node types and node
-fields can be annotated with pg_node_attr() specifications; see
-further documentation in src/include/nodes/nodes.h.
+The node structures are plain old C structures with the first field
+being of type NodeTag.  "Inheritance" is achieved by convention:
+the first field can alternatively be of another node type.
+
+Utility functions for manipulating node structures reside in this
+directory.  Some support functions are automatically generated by the
+gen_node_support.pl script, other functions are maintained manually.
+To control the automatic generation of support functions, node types
+and node fields can be annotated with pg_node_attr() specifications;
+see further documentation in src/include/nodes/nodes.h.
 
 
 FILES IN THIS DIRECTORY (src/backend/nodes/)
@@ -60,14 +61,17 @@ Suppose you want to define a node Foo:
 1. Add the structure definition to the appropriate include/nodes/???.h file.
    If you intend to inherit from, say a Plan node, put Plan as the first field
    of your struct definition.  (The T_Foo tag is created automatically.)
-2. Check that the generated support functions in copyfuncs.c, equalfuncs.c,
-   outfuncs.c and readfuncs.c look correct.  Add attributes as necessary to
-   control the outcome.  (Except for frequently used nodes, don't bother
-   writing a creator function in makefuncs.c)
+2. Check that the generated support functions in copyfuncs.funcs.c,
+   equalfuncs.funcs.c, outfuncs.funcs.c and readfuncs.funcs.c look
+   correct.  Add attributes as necessary to control the outcome.  (For
+   some classes of node types, you don't need all four support functions.
+   Use node attributes similar to those of related node types.)
 3. Add cases to the functions in nodeFuncs.c as needed.  There are many
    other places you'll probably also need to teach about your new node
    type.  Best bet is to grep for references to one or two similar existing
    node types to find all the places to touch.
+   (Except for frequently-created nodes, don't bother writing a creator
+   function in makefuncs.c.)
 4. Consider testing your new code with COPY_PARSE_PLAN_TREES,
    WRITE_READ_PARSE_PLAN_TREES, and RAW_EXPRESSION_COVERAGE_TEST to ensure
    support has been added everywhere that it's necessary; see
@@ -79,4 +83,6 @@ tags, so you'll need to recompile the whole tree after doing this.
 because the numbers never go to disk.  But altering or removing a node
 type should usually be accompanied by an initdb-forcing catalog
 version change, since the interpretation of serialized node trees
-stored in system catalogs is affected by that.
+stored in system catalogs is affected by that.  (If the node type
+never appears in stored parse trees, as for example Plan nodes do not,
+then a catversion change is not needed to change it.)
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 86af4bf032..78c7f27cda 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -58,10 +58,11 @@ my @scalar_types = qw(
 # collect enum types
 my @enum_types;
 
+# collect types that are abstract (hence no node tag, no support functions)
 my @abstract_types = qw(Node);
 
 # Special cases that either don't have their own struct or the struct
-# is not in a header file.  We just generate node tags for them, but
+# is not in a header file.  We generate node tags for them, but
 # they otherwise don't participate in node support.
 my @extra_tags = qw(
 	IntList OidList XidList
@@ -73,7 +74,9 @@ my @extra_tags = qw(
 # This is a regular node, but we skip parsing it from its header file
 # since we won't use its internal structure here anyway.
 push @node_types, qw(List);
-# See special treatment in outNode() and nodeRead().
+# Lists are specially treated in all four support files, too.
+push @no_copy, qw(List);
+push @no_equal, qw(List);
 push @no_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
@@ -262,8 +265,8 @@ foreach my $infile (@ARGV)
 					$node_type_info{$in_struct}->{field_types} = \%ft;
 					$node_type_info{$in_struct}->{field_attrs} = \%fa;
 
-					# Nodes from these files don't need to be
-					# supported, except the node tags.
+					# Nodes from these files don't need support functions,
+					# just node tags.
 					if (elem basename($infile),
 						qw(execnodes.h trigger.h event_trigger.h amapi.h tableam.h
 							tsmapi.h fdwapi.h tuptable.h replnodes.h supportnodes.h))
@@ -424,7 +427,6 @@ foreach my $n (@node_types)
 	my $struct_no_copy = (elem $n, @no_copy);
 	my $struct_no_equal = (elem $n, @no_equal);
 	next if $struct_no_copy && $struct_no_equal;
-	next if $n eq 'List';
 
 	print $cfs "\t\tcase T_${n}:\n".
 	  "\t\t\tretval = _copy${n}(from);\n".
@@ -463,11 +465,11 @@ _equal${n}(const $n *a, const $n *b)
 		my $copy_as_field;
 		foreach my $a (@a)
 		{
-			if ($a =~ /^array_size.([\w.]+)/)
+			if ($a =~ /^array_size\(([\w.]+)\)$/)
 			{
 				$array_size_field = $1;
 			}
-			elsif ($a =~ /^copy_as.([\w.]+)/)
+			elsif ($a =~ /^copy_as\(([\w.]+)\)$/)
 			{
 				$copy_as_field = $1;
 			}
@@ -478,7 +480,7 @@ _equal${n}(const $n *a, const $n *b)
 		}
 
 		# override type-specific copy method if copy_as is specified
-		if ($copy_as_field)
+		if (defined $copy_as_field)
 		{
 			print $cff "\tnewnode->$f = $copy_as_field;\n" unless $copy_ignore;
 			$copy_ignore = 1;
@@ -509,6 +511,7 @@ _equal${n}(const $n *a, const $n *b)
 			}
 			else
 			{
+				# All CoercionForm fields are treated as equal_ignore
 				print $eff "\tCOMPARE_SCALAR_FIELD($f);\n" unless $equal_ignore || $t eq 'CoercionForm';
 			}
 		}
@@ -516,7 +519,7 @@ _equal${n}(const $n *a, const $n *b)
 		elsif ($t =~ /(\w+)\*/ and elem $1, @scalar_types)
 		{
 			my $tt = $1;
-			if (!$array_size_field)
+			if (!defined $array_size_field)
 			{
 				die "no array size defined for $n.$f of type $t";
 			}
@@ -595,7 +598,7 @@ foreach my $n (@node_types)
 		next unless elem $n, @keep;
 	}
 
-	my $struct_no_read = (elem $n, @no_read);
+	my $no_read = (elem $n, @no_read);
 
 	# output format starts with upper case node type name
 	my $N = uc $n;
@@ -605,7 +608,7 @@ foreach my $n (@node_types)
 	  "\t\t\t\tbreak;\n";
 
 	print $rfs "\telse if (MATCH(\"$N\", " . length($N) . "))\n".
-	  "\t\treturn_value = _read${n}();\n" unless $struct_no_read;
+	  "\t\treturn_value = _read${n}();\n" unless $no_read;
 
 	next if elem $n, @custom_read_write;
 
@@ -623,21 +626,20 @@ _read${n}(void)
 {
 \tREAD_LOCALS($n);
 
-" unless $struct_no_read;
+" unless $no_read;
 
 	# print instructions for each field
 	foreach my $f (@{$node_type_info{$n}->{fields}})
 	{
 		my $t = $node_type_info{$n}->{field_types}{$f};
 		my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
-		my $no_read = $struct_no_read;
 
 		# extract per-field attributes
 		my $read_write_ignore = 0;
 		my $read_as_field;
 		foreach my $a (@a)
 		{
-			if ($a =~ /^read_as.([\w.]+)/)
+			if ($a =~ /^read_as\(([\w.]+)\)$/)
 			{
 				$read_as_field = $1;
 			}
@@ -647,17 +649,18 @@ _read${n}(void)
 			}
 		}
 
-		# override type-specific read method if read_as is specified
-		if ($read_as_field)
-		{
-			print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
-			$no_read = 1;
-		}
-
-		# check this after handling read_as
 		if ($read_write_ignore)
 		{
+			# nothing to do if no_read
 			next if $no_read;
+			# for read_write_ignore with read_as(), emit the appropriate
+			# assignment on the read side and move on.
+			if (defined $read_as_field)
+			{
+				print $rff "\tlocal_node->$f = $read_as_field;\n";
+				next;
+			}
+			# else, bad specification
 			die "$n.$f must not be marked read_write_ignore\n";
 		}
 
@@ -751,13 +754,13 @@ _read${n}(void)
 			my $array_size_field;
 			foreach my $a (@a)
 			{
-				if ($a =~ /^array_size.([\w.]+)/)
+				if ($a =~ /^array_size\(([\w.]+)\)$/)
 				{
 					$array_size_field = $1;
 					last;
 				}
 			}
-			if (!$array_size_field)
+			if (!defined $array_size_field)
 			{
 				die "no array size defined for $n.$f of type $t";
 			}
@@ -822,6 +825,13 @@ _read${n}(void)
 		{
 			die "could not handle type \"$t\" in struct \"$n\" field \"$f\"";
 		}
+
+		# for read_as() without read_write_ignore, we have to read the value
+		# that outfuncs.c wrote and then overwrite it.
+		if (defined $read_as_field)
+		{
+			print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
+		}
 	}
 
 	print $off "}
@@ -829,7 +839,7 @@ _read${n}(void)
 	print $rff "
 \tREAD_DONE();
 }
-" unless $struct_no_read;
+" unless $no_read;
 }
 
 close $off;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 83aa6c53bd..bf24248405 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -602,9 +602,8 @@ typedef enum NodeTag
  *
  * Node types can be supertypes of other types whether or not they are marked
  * abstract: if a node struct appears as the first field of another struct
- * type, then it is the supertype of that type.  The no_copy, no_equal,
- * no_copy_equal, and no_read node attributes are automatically inherited
- * from the supertype.
+ * type, then it is the supertype of that type.  The no_copy, no_equal, and
+ * no_read node attributes are automatically inherited from the supertype.
  *
  * Valid node field attributes:
  *
