my @no_read;
 # node types we don't want read/write support for
 my @no_read_write;
+# node types that have handmade read/write support
+my @special_read_write;
 # node types we don't want any support functions for, just node tags
 my @nodetag_only;
 
 # since we won't use its internal structure here anyway.
 push @node_types, qw(List);
 # 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);
+# (Ideally we'd mark List as "special copy/equal" not "no copy/equal".
+# But until there's other use-cases for that, just hot-wire the tests
+# that would need to distinguish.)
+push @no_copy,            qw(List);
+push @no_equal,           qw(List);
+push @special_read_write, qw(List);
 
 # Nodes with custom copy/equal implementations are skipped from
 # .funcs.c but need case statements in .switch.c.
                        }
                        elsif ($attr eq 'special_read_write')
                        {
-                           # This attribute is called
-                           # "special_read_write" because there is
-                           # special treatment in outNode() and
-                           # nodeRead() for these nodes.  For this
-                           # script, it's the same as
-                           # "no_read_write", but calling the
-                           # attribute that externally would probably
-                           # be confusing, since read/write support
-                           # does in fact exist.
-                           push @no_read_write, $in_struct;
+                           push @special_read_write, $in_struct;
                        }
                        elsif ($attr =~ /^nodetag_number\((\d+)\)$/)
                        {
 {
 " unless $struct_no_equal;
 
+   # track already-processed fields to support field order checks
+   my %previous_fields;
+
    # print instructions for each field
    foreach my $f (@{ $node_type_info{$n}->{fields} })
    {
            if ($a =~ /^array_size\(([\w.]+)\)$/)
            {
                $array_size_field = $1;
+               # insist that we copy or compare the array size first!
+               die
+                 "array size field $array_size_field for field $n.$f must precede $f\n"
+                 if (!$previous_fields{$array_size_field});
            }
            elsif ($a =~ /^copy_as\(([\w.]+)\)$/)
            {
        elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
            and elem $1, @node_types)
        {
+           die
+             "node type \"$1\" lacks copy support, which is required for struct \"$n\" field \"$f\"\n"
+             if (elem $1, @no_copy or elem $1, @nodetag_only)
+             and $1 ne 'List'
+             and !$copy_ignore;
+           die
+             "node type \"$1\" lacks equal support, which is required for struct \"$n\" field \"$f\"\n"
+             if (elem $1, @no_equal or elem $1, @nodetag_only)
+             and $1 ne 'List'
+             and !$equal_ignore;
+
            print $cff "\tCOPY_NODE_FIELD($f);\n"    unless $copy_ignore;
            print $eff "\tCOMPARE_NODE_FIELD($f);\n" unless $equal_ignore;
        }
            die
              "could not handle type \"$t\" in struct \"$n\" field \"$f\"\n";
        }
+
+       $previous_fields{$f} = 1;
    }
 
    print $cff "
    next if elem $n, @abstract_types;
    next if elem $n, @nodetag_only;
    next if elem $n, @no_read_write;
+   next if elem $n, @special_read_write;
 
    my $no_read = (elem $n, @no_read);
 
 ";
    }
 
+   # track already-processed fields to support field order checks
+   # (this isn't quite redundant with the previous loop, since
+   # we may be considering structs that lack copy/equal support)
+   my %previous_fields;
+
    # print instructions for each field
    foreach my $f (@{ $node_type_info{$n}->{fields} })
    {
            if ($a =~ /^array_size\(([\w.]+)\)$/)
            {
                $array_size_field = $1;
+               # insist that we read the array size first!
+               die
+                 "array size field $array_size_field for field $n.$f must precede $f\n"
+                 if (!$previous_fields{$array_size_field} && !$no_read);
            }
            elsif ($a =~ /^read_as\(([\w.]+)\)$/)
            {
        elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
            and elem $1, @node_types)
        {
+           die
+             "node type \"$1\" lacks write support, which is required for struct \"$n\" field \"$f\"\n"
+             if (elem $1, @no_read_write or elem $1, @nodetag_only);
+           die
+             "node type \"$1\" lacks read support, which is required for struct \"$n\" field \"$f\"\n"
+             if (elem $1, @no_read or elem $1, @nodetag_only)
+             and !$no_read;
+
            print $off "\tWRITE_NODE_FIELD($f);\n";
            print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read;
        }
        {
            print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read;
        }
+
+       $previous_fields{$f} = 1;
    }
 
    print $off "}