# for our tantrums
my class X::TypeCheck { ... };
my class X::TypeCheck::Splice { ... }

# An Array is a List that ensures every item added to it is in a Scalar
# container. It also supports push, pop, shift, unshift, splice, BIND-POS,
# and so forth.
my class Array { # declared in BOOTSTRAP
    # class Array is List
    #     has Mu $!descriptor;

    my class ArrayReificationTarget {
        has $!target;
        has $!descriptor;

        method new(\target, Mu \descriptor) {
            nqp::bindattr((my \rt = nqp::create(self)),self,'$!target',target);
            nqp::p6bindattrinvres(rt,self,'$!descriptor',descriptor)
        }

        method push(Mu \value --> Nil) {
            nqp::push($!target, nqp::p6scalarwithvalue($!descriptor, value));
        }

        method append(IterationBuffer:D \buffer --> Nil) {
            nqp::while(
              nqp::elems(buffer),
              nqp::push($!target,
                nqp::p6scalarwithvalue($!descriptor,nqp::shift(buffer))
              )
            );
        }
    }

    my class ListReificationTarget {
        has $!target;

        method new(\target) {
            nqp::p6bindattrinvres(nqp::create(self), self, '$!target', target);
        }

        method push(Mu \value --> Nil) {
            nqp::push($!target,nqp::decont(value));
        }

        method append(IterationBuffer:D \buffer --> Nil) {
            nqp::splice($!target,buffer,nqp::elems($!target),0)
        }
    }

    multi method clone(Array:D: --> Array:D) {
        my \iter := self.iterator;
        my \result := nqp::p6bindattrinvres(
          nqp::create(self),Array,'$!descriptor',nqp::clone($!descriptor)
        );

        nqp::if(
          nqp::eqaddr(
            IterationEnd,
            iter.push-until-lazy:
              my \target := ArrayReificationTarget.new(
                (my \buffer := nqp::create(IterationBuffer)),
                nqp::clone($!descriptor))),
          nqp::p6bindattrinvres(result, List, '$!reified', buffer),
          nqp::stmts(
            nqp::bindattr(result, List, '$!reified', buffer),
            nqp::bindattr((my \todo := nqp::create(List::Reifier)),
              List::Reifier,'$!current-iter', iter),
            nqp::bindattr(todo,
              List::Reifier,'$!reified', buffer),
            nqp::bindattr(todo,
              List::Reifier,'$!reification-target', target),
            nqp::p6bindattrinvres(result, List, '$!todo', todo)
          )
        )
    }

    my class Todo does Iterator {
        has int $!i;
        has $!array;
        has $!reified;
        has $!todo;
        has $!descriptor;

        method !SET-SELF(\array) {
            $!i           = -1;
            $!array      := array;
            $!reified    :=
              nqp::ifnull(
                nqp::getattr( array,List,'$!reified'),
                nqp::bindattr(array,List,'$!reified',
                  nqp::create(IterationBuffer))
              );
            $!todo       := nqp::getattr(array,List, '$!todo');
            $!descriptor := nqp::getattr(array,Array,'$!descriptor');
            self
        }
        method new(\array) { nqp::create(self)!SET-SELF(array) }

        method pull-one() is raw {
            nqp::ifnull(
              nqp::atpos($!reified,++$!i),
              nqp::if(
                nqp::islt_i($!i,nqp::elems($!reified)),
                self!hole($!i),
                nqp::if(
                  nqp::isconcrete($!todo),
                  nqp::if(
                    nqp::islt_i(
                      $!i,
                      $!todo.reify-at-least(nqp::add_i($!i,1))
                    ),
                    nqp::atpos($!reified,$!i), # cannot be nqp::null
                    self!done
                  ),
                  IterationEnd
                )
              )
            )
        }
        method !hole(int $i) is raw {
            nqp::p6scalarfromcertaindesc(
              ContainerDescriptor::BindArrayPos.new($!descriptor,$!reified,$i)
            )
        }
        method !done() is raw {
            $!todo := nqp::bindattr($!array,List,'$!todo',Mu);
            IterationEnd
        }

        method push-until-lazy(\target) {
            nqp::if(
              nqp::isconcrete($!todo),
              nqp::stmts(
                (my int $elems = $!todo.reify-until-lazy),
                (my int $i = $!i),   # lexicals faster than attributes
                nqp::while(   # doesn't sink
                  nqp::islt_i(++$i,$elems),
                  target.push(nqp::atpos($!reified,$i))
                ),
                nqp::if(
                  $!todo.fully-reified,
                  nqp::stmts(
                    ($!i = $i),
                    self!done
                  ),
                  nqp::stmts(
                    ($!i = nqp::sub_i($elems,1)),
                    Mu
                  )
                )
              ),
              nqp::stmts(
                ($elems = nqp::elems($!reified)),
                ($i = $!i),
                nqp::while(   # doesn't sink
                  nqp::islt_i(++$i,$elems),
                  target.push(
                    nqp::ifnull(nqp::atpos($!reified,$i),self!hole($i))
                  )
                ),
                ($!i = $i),
                IterationEnd
              )
            )
        }

        method is-lazy() { $!todo.DEFINITE && $!todo.is-lazy }
    }
    multi method iterator(Array:D: --> Iterator:D) {
        nqp::isconcrete(nqp::getattr(self,List,'$!todo'))
          ?? Todo.new(self)                      # something to iterate over
          !! nqp::isconcrete(nqp::getattr(self,List,'$!reified'))
            ?? Rakudo::Iterator.ReifiedArray(    # everything is already there
                 self,
                 nqp::getattr(self,Array,'$!descriptor')
               )
            !! Rakudo::Iterator.Empty            # nothing now or in the future
    }
    method from-iterator(Array:U: Iterator $iter --> Array:D) {
        nqp::if(
          nqp::eqaddr(
            $iter.push-until-lazy(
              my \target := ArrayReificationTarget.new(
                (my \buffer := nqp::create(IterationBuffer)),
                BEGIN nqp::getcurhllsym('default_cont_spec')
              )
            ),
            IterationEnd
          ),
          nqp::p6bindattrinvres(nqp::create(self),List,'$!reified',buffer),
          nqp::stmts(
            nqp::bindattr((my \result := nqp::create(self)),
              List,'$!reified',buffer),
            nqp::bindattr((my \todo := nqp::create(List::Reifier)),
              List::Reifier,'$!current-iter',$iter),
            nqp::bindattr(todo,
              List::Reifier,'$!reified',buffer),
            nqp::bindattr(todo,
              List::Reifier,'$!reification-target',target),
            nqp::p6bindattrinvres(result,List,'$!todo',todo)
          )
        )
    }
    method from-list(Array:U: Mu \list --> Array:D) {
        my \params   := nqp::getattr(list,List,'$!reified');
        my int $elems = list.elems;  # reifies
        my int $i     = -1;
        my \reified  := nqp::create(IterationBuffer);
        nqp::while(
          nqp::islt_i(++$i,$elems),
          nqp::bindpos(
            reified, $i,
            nqp::p6scalarwithvalue(
              (BEGIN nqp::getcurhllsym('default_cont_spec')),
              nqp::decont(nqp::atpos(params,$i))
            )
          )
        );
        nqp::p6bindattrinvres(nqp::create(Array),List,'$!reified',reified)
    }

    # handle non-straightforward shapes
    method !difficult-shape(\shape --> Array:D) {
        nqp::if(
          Metamodel::EnumHOW.ACCEPTS(shape.HOW),
          self.set-shape(shape.^elems),
          nqp::stmts(
            warn("Ignoring [{ shape.^name }] as shape specification, did you mean 'my { shape.^name } @foo' ?"),
            nqp::create(self)
          )
        )
    }

    proto method new(|) {*}
    multi method new(Array: :$shape! --> Array:D) {
        nqp::isconcrete($shape)
          ?? self.set-shape($shape)
          !! self!difficult-shape($shape)
    }
    multi method new(Array: --> Array:D) {
        nqp::create(self)
    }
    multi method new(Array: \values, :$shape! --> Array:D) {
        (nqp::isconcrete($shape)
          ?? self.set-shape($shape)
          !! self!difficult-shape($shape)
        ).STORE(values)
    }
    multi method new(Array: \values --> Array:D) {
        nqp::create(self).STORE(values)
    }
    multi method new(Array: **@values is raw, :$shape! --> Array:D) {
        (nqp::isconcrete($shape)
          ?? self.set-shape($shape)
          !! self!difficult-shape($shape)
        ).STORE(@values)
    }
    multi method new(Array: **@values is raw --> Array:D) {
        nqp::create(self).STORE(@values)
    }

    proto method STORE(Array:D: |) {*}
    multi method STORE(Array:D: Iterable:D \iterable --> Array:D) {
        $!descriptor := $!descriptor.next
            if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute);
        my \buffer = nqp::create(IterationBuffer);
        nqp::if(
          nqp::iscont(iterable),
          nqp::stmts(                          # only a single element
            nqp::push(
              buffer,
              nqp::p6scalarwithvalue($!descriptor,iterable)
            ),
            nqp::bindattr(self,List,'$!todo',Mu)
          ),
          nqp::if(                             # a real iterator with N elems
            nqp::eqaddr(
              (my \iter = iterable.iterator).push-until-lazy(
                (my \target = ArrayReificationTarget.new(
                  buffer,nqp::decont($!descriptor)
                ))
              ),
              IterationEnd
            ),
            nqp::bindattr(self,List,'$!todo',Mu),  # exhausted
            nqp::stmts(                            # still left to do
              nqp::bindattr(self,List,'$!todo',
                my \todo = nqp::create(List::Reifier)),
              nqp::bindattr(todo,List::Reifier,'$!reified',buffer),
              nqp::bindattr(todo,List::Reifier,'$!current-iter',iter),
              nqp::bindattr(todo,List::Reifier,'$!reification-target',target),
            )
          )
        );
        nqp::p6bindattrinvres(self,List,'$!reified',buffer)
    }
    multi method STORE(Array:D: QuantHash:D \qh --> Array:D) {
        $!descriptor := $!descriptor.next
            if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute);
        my \buffer = nqp::create(IterationBuffer);
        nqp::iscont(qh)
          ?? nqp::push(buffer,nqp::p6scalarwithvalue($!descriptor,qh))
          !! qh.iterator.push-all(
               ArrayReificationTarget.new(buffer,nqp::decont($!descriptor))
             );
        nqp::bindattr(self,List,'$!todo',Mu);  # exhausted
        nqp::p6bindattrinvres(self,List,'$!reified',buffer)
    }
    multi method STORE(Array:D: Mu \item --> Array:D) {
        $!descriptor := $!descriptor.next
            if nqp::eqaddr($!descriptor.WHAT, ContainerDescriptor::UninitializedAttribute);
        nqp::push(
          (my \buffer = nqp::create(IterationBuffer)),
          nqp::p6scalarwithvalue($!descriptor, item)
        );
        nqp::bindattr(self,List,'$!todo',Mu);
        nqp::p6bindattrinvres(self,List,'$!reified',buffer)
    }

    method reification-target(Array:D: --> ArrayReificationTarget:D) {
        ArrayReificationTarget.new(
            nqp::getattr(self, List, '$!reified'),
            nqp::decont($!descriptor))
    }

    multi method Slip(Array:D: --> Slip:D) {

       # A Slip-With-Descripto is a special kind of Slip that also has a
       # descriptor to be able to generate containers for null elements that
       # have type and default information.
        my class Slip-With-Descriptor is Slip {
            has $!descriptor;

            method iterator() {
                Rakudo::Iterator.ReifiedArray(self,$!descriptor)
            }

            multi method AT-POS(Int:D $pos) {
                nqp::ifnull(
                  nqp::atpos(nqp::getattr(self,List,'$!reified'),$pos),
                  nqp::p6scalarfromcertaindesc(
                    ContainerDescriptor::BindArrayPos.new(
                      $!descriptor, nqp::getattr(self,List,'$!reified'), $pos
                    )
                  )
                )
            }
            method default() { $!descriptor.default }
        }
        BEGIN Slip-With-Descriptor.^set_name("Slip");

        nqp::isconcrete(nqp::getattr(self,List,'$!todo'))
             # We're not fully reified, and so have internal mutability still.
             # The safe thing to do is to take an iterator of ourself and build
             # the Slip out of that.
          ?? Slip.from-iterator(self.iterator)
             # We're fully reified.  Make a Slip that shares our reified buffer
             # but that will fill in default values for nulls.
          !! nqp::isconcrete(nqp::getattr(self,List,'$!reified'))
            ?? nqp::p6bindattrinvres(
                 nqp::p6bindattrinvres(
                   nqp::create(Slip-With-Descriptor),
                   Slip-With-Descriptor,
                   '$!descriptor',
                   $!descriptor
                 ),
                 List,
                 '$!reified',
                 nqp::clone(nqp::getattr(self,List,'$!reified'))
               )
            !! nqp::create(Slip)
    }

    method FLATTENABLE_LIST() is implementation-detail {
        nqp::if(
          nqp::isconcrete(nqp::getattr(self,List,'$!todo')),
          nqp::stmts(
            nqp::getattr(self,List,'$!todo').reify-all,
            nqp::getattr(self,List,'$!reified')
          ),
          nqp::if(
            nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::stmts(
              nqp::if(
                (my int $elems = nqp::elems($reified)),
                nqp::stmts(
                  (my int $i = -1),
                  nqp::while(
                    nqp::islt_i(++$i,$elems),
                    nqp::if(
                      nqp::isnull(nqp::atpos($reified,$i)),
                      nqp::bindpos(
                        $reified,
                        $i,
                        nqp::p6scalarfromcertaindesc($!descriptor)
                      )
                    )
                  )
                )
              ),
              nqp::getattr(self,List,'$!reified')
            ),
            nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer))
          )
        )
    }

    multi method flat(Array:D: $levels = Whatever, :$hammer = False) {
        Seq.new: $hammer
          ?? Rakudo::Iterator.Flat: self.iterator, $levels, $hammer
          !! self.iterator  # not hammering, always conted, so no-op
    }

    method reverse(Array:D: --> Seq:D) is nodal {
        self.is-lazy    # reifies
          ?? self.fail-iterator-cannot-be-lazy('.reverse')
          !! Seq.new: nqp::getattr(self,List,'$!reified')
            ?? Rakudo::Iterator.ReifiedReverse(self, $!descriptor)
            !! Rakudo::Iterator.Empty
    }

    method rotate(List:D: Int(Cool) $rotate = 1 --> Seq:D) is nodal {
        self.is-lazy    # reifies
          ?? self.fail-iterator-cannot-be-lazy('.rotate')
          !! Seq.new: nqp::getattr(self,List,'$!reified')
            ?? Rakudo::Iterator.ReifiedRotate($rotate, self, $!descriptor)
            !! Rakudo::Iterator.Empty
    }

    multi method List(Array:D: :view($)! --> List:D) is implementation-detail {
        self.is-lazy    # reifies
          ?? self.throw-iterator-cannot-be-lazy('.List')
          !! nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified'))
            ?? $reified.List
            !! nqp::create(List)
    }
    multi method List(Array:D: --> List:D) {
        nqp::if(
          self.is-lazy,                         # can't make a List
          self.throw-iterator-cannot-be-lazy('.List'),

          nqp::if(                              # all reified
            nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::stmts(                         # make cow copy
              (my int $elems = nqp::elems($reified)),
              (my $cow := nqp::setelems(nqp::create(IterationBuffer),$elems)),
              (my int $i = -1),
              nqp::while(
                nqp::islt_i(++$i,$elems),
                nqp::bindpos(
                  $cow,
                  $i,
                  nqp::ifnull(nqp::decont(nqp::atpos($reified,$i)),Nil)
                ),
              ),
              $cow.List
            ),
            nqp::create(List)                   # was empty, is empty
          )
        )
    }

    method shape(Array: --> List:D) { (*,) }  # should probably be Array:D:

    multi method AT-POS(Array:D: uint $pos) is raw {
        nqp::ifnull(
          nqp::if(
            nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::atpos($reified, $pos),  # can also be null
            nqp::null
          ),
          self!AT_POS_SLOW($pos)
        )
    }
    multi method AT-POS(Array:D: Int:D $pos) is raw {
        nqp::ifnull(
          nqp::if(
            nqp::bitand_i(
              nqp::isge_i($pos,0),
              nqp::isconcrete(
                my $reified := nqp::getattr(self,List,'$!reified')
              )
            ),
            nqp::atpos($reified, $pos),  # can also be null
            nqp::null
          ),
          self!AT_POS_SLOW($pos)
        )
    }

    # handle any lookup that is not simple
    method !AT_POS_SLOW(int $pos) is raw {
        nqp::if(
          nqp::islt_i($pos, 0),
          self!INDEX_OOR($pos),
          nqp::if(
            nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::if(
              nqp::islt_i($pos,nqp::elems($reified)),
              self!AT_POS_CONTAINER($pos),        # it's a hole
              nqp::if(                           # too far out, try reifying
                nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
                nqp::stmts(
                  $todo.reify-at-least(nqp::add_i($pos,1)),
                  nqp::ifnull(
                    nqp::atpos($reified,$pos),   # reified ok
                    self!AT_POS_CONTAINER($pos)  # reifier didn't reach
                  )
                ),
                self!AT_POS_CONTAINER($pos)      # create an outlander
              )
            ),
            # no reified, implies no todo
            nqp::stmts(                          # create reified
              nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)),
              self!AT_POS_CONTAINER($pos)        # create an outlander
            )
          )
        )
    }
    method !AT_POS_CONTAINER(int $pos) is raw {
        my $desc := $!descriptor;
        my $scalar := nqp::create(Scalar);
        nqp::bindattr($scalar, Scalar, '$!value', nqp::isnull($desc)
            ?? Any
            !! nqp::getattr($desc, ContainerDescriptor, '$!default'));
        nqp::bindattr($scalar, Scalar, '$!descriptor',
            ContainerDescriptor::BindArrayPos.new(
                $desc, nqp::getattr(self,List,'$!reified'), $pos));
        $scalar
    }

    multi method ASSIGN-POS(Array:D: uint $pos, Mu \assignee) is raw {
        nqp::bitand_i(
          nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
          nqp::not_i(nqp::isconcrete(nqp::getattr(self,List,'$!todo'))),
        ) ?? nqp::p6assign(
               nqp::ifnull(
                 nqp::atpos($reified, $pos),
                 nqp::bindpos(
                   $reified,
                   $pos,
                   nqp::p6bindattrinvres(
                     nqp::create(Scalar),Scalar,'$!descriptor',$!descriptor
                   )
                 )
               ),
               nqp::decont(assignee)
             )
          !! self!ASSIGN_POS_SLOW_PATH($pos, assignee)
    }
    multi method ASSIGN-POS(Array:D: Int:D $pos, Mu \assignee) is raw {
        nqp::isge_i($pos,0)
          ?? nqp::bitand_i(  # should refer to uint candidate when that inlines
               nqp::isconcrete(my \reified := nqp::getattr(self,List,'$!reified')),
               nqp::not_i(nqp::isconcrete(nqp::getattr(self,List,'$!todo'))),
             )
            ?? nqp::p6assign(
                 nqp::ifnull(
                   nqp::atpos(reified, $pos),
                   nqp::bindpos(
                     reified,
                     $pos,
                     nqp::p6bindattrinvres(
                       nqp::create(Scalar),Scalar,'$!descriptor',$!descriptor
                     )
                   )
                 ),
                 nqp::decont(assignee)
               )
            !! self!ASSIGN_POS_SLOW_PATH($pos, assignee)
          !! self!INDEX_OOR($pos)
    }

    method !ASSIGN_POS_SLOW_PATH(Array:D: int $pos, Mu \assignee) is raw {
        my \reified := nqp::getattr(self,List,'$!reified');
        nqp::p6assign(
          nqp::if(
            nqp::isconcrete(reified),
            nqp::ifnull(
              nqp::atpos(reified,$pos),
              nqp::if(
                nqp::islt_i($pos,nqp::elems(reified)), # it's a hole
                nqp::bindpos(
                  reified,
                  $pos,
                  nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor)
                ),
                nqp::if(
                  nqp::isconcrete(my \todo := nqp::getattr(self,List,'$!todo')),
                  nqp::stmts(                    # can reify
                    todo.reify-at-least(nqp::add_i($pos,1)),
                    nqp::ifnull(
                      nqp::atpos(reified,$pos),  # reified
                      nqp::bindpos(              # outlander
                        reified,
                        $pos,
                        nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor)
                      )
                    )
                  ),
                  nqp::bindpos(                  # outlander without todo
                    reified,
                    $pos,
                    nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor)
                  )
                )
              )
            ),
            nqp::bindpos(                        # new outlander without reified
              nqp::bindattr(self,List,'$!reified',nqp::create(IterationBuffer)),
              $pos,
              nqp::p6bindattrinvres(nqp::create(Scalar), Scalar, '$!descriptor', $!descriptor)
            )
          ),
          nqp::decont(assignee)
        )
    }

    multi method BIND-POS(Array:D: uint $pos, Mu \bindval) is raw {
        nqp::if(
          nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
          nqp::if(
            nqp::isge_i($pos, nqp::elems($reified))
              && nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
            $todo.reify-at-least(nqp::add_i($pos,1)),
          ),
          ($reified := nqp::bindattr(
            self,List,'$!reified',nqp::create(IterationBuffer)
          ))
        );
        nqp::bindpos($reified,$pos,bindval)
    }
    multi method BIND-POS(Array:D: Int:D $pos, Mu \bindval) is raw {
        nqp::if(
          nqp::islt_i($pos,0),
          self!INDEX_OOR($pos),
          nqp::stmts(  # should refer to uint candidate when that inlines
            nqp::if(
              nqp::isconcrete(
                my $reified := nqp::getattr(self,List,'$!reified')
              ),
              nqp::if(
                nqp::isge_i($pos, nqp::elems($reified))
                  && nqp::isconcrete(
                       my $todo := nqp::getattr(self,List,'$!todo')
                     ),
                $todo.reify-at-least(nqp::add_i($pos,1)),
              ),
              ($reified := nqp::bindattr(
                self,List,'$!reified',nqp::create(IterationBuffer)
              ))
            ),
            nqp::bindpos($reified,$pos,bindval)
          )
        )
    }

    method !remove-nulls-from-end(uint $from --> Nil) {
        my int $i = $from;
        nqp::unless(
          nqp::isconcrete(nqp::getattr(self,List,'$!todo')),
          nqp::stmts(
            (my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::while(
              (nqp::isge_i(--$i,0)
                && nqp::not_i(nqp::existspos($reified,$i))),
              nqp::null
            ),
            nqp::setelems($reified,nqp::add_i($i,1))
          )
        )
    }

    multi method DELETE-POS(Array:D: uint $pos) is raw {
        nqp::if(
          nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
          nqp::stmts(
            nqp::if(
              nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
              $todo.reify-at-least(nqp::add_i($pos,1)),
            ),
            nqp::if(
              nqp::isle_i(                               # something to delete
                $pos,my int $end = nqp::sub_i(nqp::elems($reified),1)),
              nqp::stmts(
                (my $value := nqp::ifnull(               # save the value
                  nqp::atpos($reified,$pos),
                  self.default
                )),
                nqp::bindpos($reified,$pos,nqp::null),   # remove this one
                nqp::if(
                  nqp::iseq_i($pos,$end),
                  self!remove-nulls-from-end($pos)
                ),
                $value                                   # value, if any
              ),
              self.default                               # outlander
            ),
          ),
          self.default                                 # no elements
        )
    }
    multi method DELETE-POS(Array:D: Int:D $pos) is raw {
        nqp::if(
          nqp::islt_i($pos,0),
          self!INDEX_OOR($pos),
          nqp::if(  # should refer to the uint candidate when that inlines
            nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified')),
            nqp::stmts(
              nqp::if(
                nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
                $todo.reify-at-least(nqp::add_i($pos,1)),
              ),
              nqp::if(
                nqp::isle_i(                               # something to delete
                  $pos,my int $end = nqp::sub_i(nqp::elems($reified),1)),
                nqp::stmts(
                  (my $value := nqp::ifnull(               # save the value
                    nqp::atpos($reified,$pos),
                    self.default
                  )),
                  nqp::bindpos($reified,$pos,nqp::null),   # remove this one
                  nqp::if(
                    nqp::iseq_i($pos,$end),
                    self!remove-nulls-from-end($pos)
                  ),
                  $value                                   # value, if any
                ),
                self.default                               # outlander
              ),
            ),
            self.default                                 # no elements
          )
        )
    }

    method !INDEX_OOR($pos) {
      X::OutOfRange.new(
          :what($*INDEX // 'Index'), :got($pos), :range<0..^Inf>
      ).Failure
    }

    # MUST have a separate Slip variant to have it slip
    multi method push(Array:D: Slip \value --> Array:D) {
        self.is-lazy
          ?? self.throw-iterator-cannot-be-lazy('push to')
          !! self!append-list(value)
    }
    multi method push(Array:D: \value --> Array:D) {
        nqp::if(
          self.is-lazy,
          self.throw-iterator-cannot-be-lazy('push to'),
          nqp::stmts(
            nqp::push(
              nqp::if(
                nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                nqp::getattr(self,List,'$!reified'),
                nqp::bindattr(self,List,'$!reified',
                  nqp::create(IterationBuffer))
              ),
              nqp::p6scalarwithvalue($!descriptor,value)
            ),
            self
          )
        )
    }
    multi method push(Array:D: **@values is raw --> Array:D) {
        self.is-lazy
          ?? self.throw-iterator-cannot-be-lazy('push to')
          !! self!append-list(@values)
    }

    multi method append(Array:D: \value --> Array:D) {
        nqp::if(
          self.is-lazy,
          self.throw-iterator-cannot-be-lazy('append to'),
          nqp::if(
            (nqp::iscont(value) || nqp::not_i(nqp::istype(value, Iterable))),
            nqp::stmts(
              nqp::push(
                nqp::if(
                  nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                  nqp::getattr(self,List,'$!reified'),
                  nqp::bindattr(self,List,'$!reified',
                    nqp::create(IterationBuffer))
                ),
                nqp::p6scalarwithvalue($!descriptor,value)
              ),
              self
            ),
            self!append-list(value.list)
          )
        )
    }
    multi method append(Array:D: **@values is raw --> Array:D) {
        self.is-lazy
          ?? self.throw-iterator-cannot-be-lazy('append to')
          !! self!append-list(@values)
    }
    method !append-list(Array:D: @values --> Array:D) {
        nqp::if(
          nqp::eqaddr(
            @values.iterator.push-until-lazy(
              ArrayReificationTarget.new(
                nqp::if(
                  nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                  nqp::getattr(self,List,'$!reified'),
                  nqp::bindattr(self,List,'$!reified',
                    nqp::create(IterationBuffer))
                ),
                nqp::decont($!descriptor)
              )
            ),
            IterationEnd
          ),
          self,
          self.throw-iterator-cannot-be-lazy('push')
        )
    }

    multi method unshift(Array:D: Slip \value --> Array:D) {
        self!prepend-list(value)
    }
    multi method unshift(Array:D: \value --> Array:D) {
        nqp::stmts(
          nqp::unshift(
            nqp::if(
              nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
              nqp::getattr(self,List,'$!reified'),
              nqp::bindattr(self,List,'$!reified',
                nqp::create(IterationBuffer))
            ),
            nqp::p6scalarwithvalue($!descriptor,value)
          ),
          self
        )
    }
    multi method unshift(Array:D: **@values is raw --> Array:D) {
        self!prepend-list(@values)
    }
    multi method prepend(Array:D: \value --> Array:D) {
        nqp::if(
          (nqp::iscont(value) || nqp::not_i(nqp::istype(value, Iterable))),
          nqp::stmts(
            nqp::unshift(
              nqp::if(
                nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                nqp::getattr(self,List,'$!reified'),
                nqp::bindattr(self,List,'$!reified',
                  nqp::create(IterationBuffer))
              ),
              nqp::p6scalarwithvalue($!descriptor,value)
            ),
            self
          ),
          self!prepend-list(value.list)
        )
    }
    multi method prepend(Array:D: **@values is raw --> Array:D) {
        self!prepend-list(@values)
    }
    method !prepend-list(Array:D: @values --> Array:D) {
        nqp::if(
          nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
          nqp::splice(nqp::getattr(self,List,'$!reified'), # prepend existing
            nqp::stmts(
              @values.iterator.push-all(
                ArrayReificationTarget.new(
                  (my $containers := nqp::create(IterationBuffer)),
                  nqp::decont($!descriptor)
                )
              ),
              $containers
            ),
            0,
            0
          ),
          @values.iterator.push-all(        # no list yet, make this it
            ArrayReificationTarget.new(
              nqp::bindattr(self,List,'$!reified',
                nqp::create(IterationBuffer)),
              nqp::decont($!descriptor)
            )
          )
        );
        self
    }

    method pop(Array:D:) is nodal {
        my $reified := nqp::getattr(self,List,'$!reified');
        self.is-lazy
          ?? self.fail-iterator-cannot-be-lazy('pop from')
          !! nqp::isconcrete($reified) && nqp::elems($reified)
            ?? nqp::pop($reified)
            !! self.fail-cannot-be-empty('pop')
    }

    method shift(Array:D:) is nodal {
        nqp::isconcrete(my $reified := nqp::getattr(self,List,'$!reified'))
          && nqp::elems($reified)
          ?? nqp::ifnull(  # handle holes
               nqp::shift($reified),
               Nil
             )
          !! nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo'))
               && $todo.reify-at-least(1)
            ?? nqp::shift($reified)
            !! self.fail-cannot-be-empty('shift')
    }

    my $empty := nqp::create(IterationBuffer); # splicing in without values
    #------ splice() candidates
    multi method splice(Array:D \SELF: --> Array:D) {
        nqp::if(
          nqp::isconcrete(nqp::getattr(SELF,List,'$!reified')),
          nqp::stmts(
            (my $result := nqp::create(SELF)),
            nqp::bindattr($result,Array,'$!descriptor',$!descriptor),
            nqp::stmts(       # transplant the internals
              nqp::bindattr($result,List,'$!reified',
                nqp::getattr(SELF,List,'$!reified')),
              nqp::if(
                nqp::isconcrete(nqp::getattr(SELF,List,'$!todo')),
                nqp::bindattr($result,List,'$!todo',
                  nqp::getattr(SELF,List,'$!todo')),
              )
            ),
            (SELF = nqp::create(SELF)),  # XXX this preserves $!descriptor ??
            $result
          ),
          nqp::p6bindattrinvres(   # nothing to return, so create new one
            nqp::create(SELF),Array,'$!descriptor',$!descriptor)
        )
    }

    #------ splice(offset) candidates
    multi method splice(Array:D: Whatever $ --> Array:D) {
        nqp::p6bindattrinvres(     # nothing to return, so create new one
          nqp::create(self),Array,'$!descriptor',$!descriptor)
    }
    multi method splice(Array:D: Callable:D $offset --> Array:D) {
        self.splice($offset(self.elems))
    }
    multi method splice(Array:D: Int:D $offset --> Array:D) {
        nqp::if(
          $offset,
          nqp::if(
            nqp::islt_i(nqp::unbox_i($offset),0),
            self!splice-offset-fail($offset),
            nqp::if(
              nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
              nqp::if(
                nqp::isge_i(
                  $todo.reify-at-least($offset),nqp::unbox_i($offset)),
                self!splice-offset(nqp::unbox_i($offset)),
                self!splice-offset-fail($offset)
              ),
              nqp::if(
                nqp::isconcrete(nqp::getattr(self,List,'$!reified'))
                  && nqp::isge_i(
                    nqp::elems(nqp::getattr(self,List,'$!reified')),
                    nqp::unbox_i($offset)
                ),
                self!splice-offset(nqp::unbox_i($offset)),
                self!splice-offset-fail($offset)
              )
            )
          ),
          self.splice       # offset 0, take the quick route out
        )
    }
    method !splice-offset(Array:D: int $offset --> Array:D) {
        my $reified := nqp::getattr(self,List,'$!reified');
        my uint $elems = nqp::elems($reified);
        my $result:= nqp::create(self);

        nqp::unless(
          nqp::iseq_i($offset,$elems),
          nqp::stmts(
            nqp::bindattr($result,List,'$!reified',nqp::slice($reified,$offset,-1)),
            nqp::splice(
              $reified,
              $empty,
              $offset,
              nqp::sub_i(nqp::elems($reified),$offset)
            ),
          )
        );
        nqp::p6bindattrinvres($result,Array,'$!descriptor',$!descriptor)
    }
    method !splice-offset-fail(Array:D: $got) {
        X::OutOfRange.new(
          :what('Offset argument to splice'), :$got, :range("0..{self.elems}")
        ).throw
    }

    #------ splice(offset,size) candidates
    multi method splice(Array:D: Whatever $, Whatever $ --> Array:D) {
        nqp::p6bindattrinvres(     # nothing to return, so create new one
          nqp::create(self),Array,'$!descriptor',$!descriptor)
    }
    multi method splice(Array:D: Whatever $, Int:D $size --> Array:D) {
        self.splice(self.elems,$size)
    }
    multi method splice(Array:D: Whatever $, Callable:D $size --> Array:D) {
        my int $elems = self.elems;
        self.splice($elems,$size(nqp::sub_i($elems,$elems)));
    }
    multi method splice(Array:D:
      Callable:D $offset, Callable:D $size
    --> Array:D) {
        my int $elems = self.elems;
        my int $from  = $offset($elems);
        self.splice($from,$size(nqp::sub_i($elems,$from)))
    }
    multi method splice(Array:D: Callable:D $offset, Whatever $ --> Array:D) {
        self.splice($offset(self.elems))
    }
    multi method splice(Array:D: Callable:D $offset, Int:D $size --> Array:D) {
        self.splice($offset(self.elems),$size)
    }
    multi method splice(Array:D: Int:D $offset, Whatever $ --> Array:D) {
        self.splice($offset)
    }
    multi method splice(Array:D: Int:D $offset, Callable:D $size --> Array:D) {
        self.splice($offset,$size(self.elems - $offset))
    }
    multi method splice(Array:D: Int:D $offset, Int:D $size --> Array:D) {
        nqp::if(
          nqp::islt_i(nqp::unbox_i($offset),0),
          self!splice-offset-fail($offset),
          nqp::if(
            nqp::islt_i(nqp::unbox_i($size),0),
            self!splice-size-fail($size,$offset),
            nqp::if(
              nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
              nqp::if(
                nqp::isge_i(
                  $todo.reify-at-least(
                    nqp::add_i(nqp::unbox_i($offset),nqp::unbox_i($size))
                  ),nqp::unbox_i($offset)),
                self!splice-offset-size(
                  nqp::unbox_i($offset),nqp::unbox_i($size)),
                self!splice-size-fail($size,$offset)
              ),
              nqp::if(
                nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                nqp::if(
                  nqp::isge_i(
                    nqp::elems(nqp::getattr(self,List,'$!reified')),
                    nqp::unbox_i($offset)),
                  self!splice-offset-size(
                    nqp::unbox_i($offset),nqp::unbox_i($size)),
                  self!splice-size-fail($size,$offset)
                ),
                nqp::if(
                  nqp::iseq_i(nqp::unbox_i($offset),0),
                  nqp::p6bindattrinvres(     # nothing to return, create new
                    nqp::create(self),Array,'$!descriptor',$!descriptor),
                  self!splice-offset-fail($offset)
                )
              )
            )
          )
        )
    }
    method !splice-offset-size(Array:D: int $offset,int $size --> Array:D) {
        my $result := self!splice-save($offset,$size,my int $removed);
        nqp::splice(
          nqp::getattr(self,List,'$!reified'),$empty,$offset,$removed
        );
        $result
    }
    method !splice-save(Array:D: int $offset,int $size, \removed --> Array:D) {
        my $reified := nqp::getattr(self,List,'$!reified');
        my $result:= nqp::create(self);
        nqp::if(
          (removed = nqp::if(
            nqp::isgt_i(nqp::add_i($offset,$size),nqp::elems($reified)),
            nqp::sub_i(nqp::elems($reified),$offset),
            $size
          )),
          nqp::bindattr(
            $result,
            List,
            '$!reified',
            nqp::slice($reified,$offset,nqp::sub_i(nqp::add_i($offset,removed),1))
          )
        );
        nqp::p6bindattrinvres($result,Array,'$!descriptor',$!descriptor)
    }
    method !splice-size-fail(Array:D: $got,$offset) {
        $offset > self.elems
          ?? self!splice-offset-fail($offset)
          !! X::OutOfRange.new(
               :what('Size argument to splice'),
               :$got,
               :range("0..^{self.elems - $offset}")
             ).throw
    }

    #------ splice(offset,size,array) candidates
    # we have these 9 multies to avoid infiniloop when incorrect types are
    # given to $offset/$size. Other attempts to resolve this showed 30%+
    # performance decreases
    multi method splice(Array:D:
      Whatever $offset, Whatever $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Whatever $offset, Callable:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Whatever $offset, Int:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Callable:D $offset, Whatever $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Callable:D $offset, Callable:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Callable:D $offset, Int:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Int:D $offset, Whatever $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Int:D $offset, Callable:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }
    multi method splice(Array:D:
      Int:D $offset, Int:D $size, **@new
    --> Array:D) {
        self.splice($offset, $size, @new)
    }

    #----- splice(offset, index, $pos) candidates
    # we want to be able to insert an itemized array ($[]) at a location
    # without flattening its contents like in the default behavior
    multi method splice(Array:D:
                        Whatever $, Whatever $, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice(self.elems,0,[$@new])
    }
    multi method splice(Array:D:
                        Whatever $, Int:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice(self.elems,$size,[$@new])
    }
    multi method splice(Array:D:
                        Whatever $, Callable:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        my int $elems = self.elems;
        self.splice($elems,$size(nqp::sub_i($elems,$elems)),[$@new])
    }
    multi method splice(Array:D:
                        Callable:D $offset, Callable:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        my int $elems = self.elems;
        my int $from  = $offset($elems);
        self.splice($from,$size(nqp::sub_i($elems,$from)),[$@new])
    }
    multi method splice(Array:D:
                        Callable:D $offset, Whatever $, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        my int $elems = self.elems;
        my int $from  = $offset($elems);
        self.splice($from,nqp::sub_i($elems,$from),[$@new])
    }
    multi method splice(Array:D:
                        Callable:D $offset, Int:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice($offset(self.elems),$size,[$@new])
    }
    multi method splice(Array:D:
                        Int:D $offset, Whatever $, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice($offset,self.elems - $offset,[$@new])
    }
    multi method splice(Array:D:
                        Int:D $offset, Callable:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice($offset,$size(self.elems - $offset),[$@new])
    }
    multi method splice(Array:D:
                        Int:D $offset, Int:D $size, @new is item
                        --> Array:D) is revision-gated("6.e")
    {
        self.splice($offset,$size,[$@new])
    }

    multi method splice(Array:D: Whatever $, Whatever $, @new --> Array:D) {
        self.splice(self.elems,0,@new)
    }
    multi method splice(Array:D: Whatever $, Int:D $size, @new --> Array:D) {
        self.splice(self.elems,$size,@new)
    }
    multi method splice(Array:D:
      Whatever $, Callable:D $size, @new
    --> Array:D) {
        my int $elems = self.elems;
        self.splice($elems,$size(nqp::sub_i($elems,$elems)),@new);
    }
    multi method splice(Array:D:
      Callable:D $offset, Callable:D $size, @new
    --> Array:D) {
        my int $elems = self.elems;
        my int $from  = $offset($elems);
        self.splice($from,$size(nqp::sub_i($elems,$from)),@new)
    }
    multi method splice(Array:D:
      Callable:D $offset, Whatever $, @new
    --> Array:D) {
        my int $elems = self.elems;
        my int $from  = $offset($elems);
        self.splice($from,nqp::sub_i($elems,$from),@new)
    }
    multi method splice(Array:D:
      Callable:D $offset, Int:D $size, @new
    --> Array:D) {
        self.splice($offset(self.elems),$size,@new)
    }
    multi method splice(Array:D:
      Int:D $offset, Whatever $, @new
    --> Array:D) {
        self.splice($offset,self.elems - $offset,@new)
    }
    multi method splice(Array:D:
      Int:D $offset, Callable:D $size, @new
    --> Array:D) {
        self.splice($offset,$size(self.elems - $offset),@new)
    }
    multi method splice(Array:D:
      Int:D $offset, Int:D $size, @new
    --> Array:D) {
        nqp::if(
          nqp::islt_i(nqp::unbox_i($offset),0),
          self!splice-offset-fail($offset),
          nqp::if(
            nqp::islt_i(nqp::unbox_i($size),0),
            self!splice-size-fail($size,$offset),
            nqp::if(
              nqp::isconcrete(my $todo := nqp::getattr(self,List,'$!todo')),
              nqp::if(
                nqp::isge_i(
                  $todo.reify-at-least(
                    nqp::add_i(nqp::unbox_i($offset),nqp::unbox_i($size))
                  ),nqp::unbox_i($offset)),
                self!splice-offset-size-new(
                  nqp::unbox_i($offset),nqp::unbox_i($size),@new),
                self!splice-size-fail($size,$offset)
              ),
              nqp::if(
                nqp::isge_i(
                  nqp::elems(nqp::if(
                    nqp::isconcrete(nqp::getattr(self,List,'$!reified')),
                    nqp::getattr(self,List,'$!reified'),
                    nqp::bindattr(self,List,'$!reified',
                      nqp::create(IterationBuffer))
                  )),
                  nqp::unbox_i($offset),
                ),
                self!splice-offset-size-new(
                  nqp::unbox_i($offset),nqp::unbox_i($size),@new),
                self!splice-offset-fail($offset)
              )
            )
          )
        )
    }
    method !splice-offset-size-new(Array:D:
      int $offset,int $size,@new
    --> Array:D) {
        nqp::if(
          nqp::eqaddr(@new.iterator.push-until-lazy(
            (my $new := nqp::create(IterationBuffer))),IterationEnd),
          nqp::if(      # reified all values to splice in
            (nqp::isnull($!descriptor) || nqp::eqaddr(self.of,Mu)),
            nqp::stmts( # no typecheck needed
              (my $result := self!splice-save($offset,$size,my int $removed)),
              nqp::splice(
                nqp::getattr(self,List,'$!reified'),$new,$offset,$removed),
              $result
            ),
            nqp::stmts( # typecheck the values first
              (my $expected := self.of),
              (my int $elems = nqp::elems($new)),
              (my int $i = -1),
              nqp::while(
                (nqp::islt_i(++$i,$elems)
                  && nqp::istype(nqp::atpos($new,$i),$expected)),
                nqp::null
              ),
              nqp::if(
                nqp::islt_i($i,$elems),   # exited loop because of wrong type
                X::TypeCheck::Splice.new(
                  :action<splice>,
                  :got(nqp::atpos($new,$i).WHAT),
                  :$expected
                ).throw,
                nqp::stmts(
                  ($result := self!splice-save($offset,$size,$removed)),
                  nqp::splice(
                    nqp::getattr(self,List,'$!reified'),$new,$offset,$removed),
                  $result
                )
              )
            )
          ),
          self.throw-iterator-cannot-be-lazy('splice in')
        )
    }

    multi method tail(Array:D: $n) {
        nqp::if(
          nqp::isconcrete(nqp::getattr(self,List,'$!todo')),
          self.Any::tail($n),
          Seq.new(
            nqp::if(
              nqp::isconcrete(
                my $reified := nqp::getattr(self,List,'$!reified')
              ) && nqp::elems($reified),
              nqp::stmts(
                (my $iterator := Rakudo::Iterator.ReifiedArray(
                  self,
                  nqp::getattr(self,Array,'$!descriptor')
                )),
                nqp::if(
                  nqp::istype($n,Callable)
                    && nqp::isgt_i((my $skip := -($n(0).Int)),0),
                  $iterator.skip-at-least($skip),
                  nqp::unless(
                    nqp::istype($n,Whatever) || $n == Inf,
                    $iterator.skip-at-least(nqp::elems($reified) - $n)
                  )
                ),
                $iterator
              ),
              Rakudo::Iterator.Empty
            )
          )
        )
    }

    proto method grab(|) {*}
    multi method grab(Array:D:) {
        self.is-lazy
          ?? self.throw-iterator-cannot-be-lazy('grab from')  # can't make List
          !! self.elems  # reifies
            ?? self.GRAB_ONE
            !! Nil
    }
    multi method grab(Array:D: Callable:D $calculate) {
        self.grab($calculate(self.elems))
    }
    multi method grab(Array:D: Whatever --> Seq:D) {
        self.grab(Inf)
    }

    my class GrabN does Iterator {
        has $!array;
        has int $!count;

        method !SET-SELF(\array,\count) {
            my int $elems = nqp::elems(nqp::getattr(array,List,'$!reified'));
            $!array := array;

            nqp::if(
              count == Inf,
              ($!count = $elems),
              nqp::if(
                nqp::isgt_i(($!count = count.Int),$elems),
                ($!count = $elems)
              )
            );
            self

        }
        method new(\a,\c) { nqp::create(self)!SET-SELF(a,c) }
        method pull-one() {
            nqp::if(
              $!count && nqp::elems(nqp::getattr($!array,List,'$!reified')),
              nqp::stmts(
                --$!count,
                $!array.GRAB_ONE
              ),
              IterationEnd
            )
        }
        method is-deterministic(--> False) { }
    }
    multi method grab(Array:D: \count --> Seq:D) {
        Seq.new(
          self.elems                         # reifies
          ?? GrabN.new(self,count)
          !! Rakudo::Iterator.Empty
        )
    }

    method GRAB_ONE(Array:D:) is implementation-detail {
        my $reified := nqp::getattr(self,List,'$!reified');
        my $value := nqp::atpos(
          $reified,
          (my int $pos = nqp::floor_n(nqp::rand_n(nqp::elems($reified)))),
        );
        nqp::splice($reified,$empty,$pos,1);
        $value
    }

    # introspection
    method name() { $!descriptor.name }

    proto method of() {*}
    multi method of(Array:U:) { Mu }
    multi method of(Array:D:) { $!descriptor.of }

    method default() { $!descriptor.default      }
    method dynamic() { $!descriptor.dynamic.Bool }

    multi method raku(Array:D \SELF: --> Str:D) {
        SELF.rakuseen('Array', {
             '$' x nqp::iscont(SELF)  # self is always deconted
             ~ '['
             ~ self.map({nqp::decont($_).raku}).join(', ')
             ~ ',' x ((try self.elems // Inf) == 1 && nqp::istype(self.AT-POS(0),Iterable))
             ~ ']'
        })
    }
    multi method WHICH(Array:D: --> ObjAt:D) { self.Mu::WHICH }

    my constant \dim2role =
      nqp::list(Array::Shaped,Array::Shaped1,Array::Shaped2,Array::Shaped3);

    proto method set-shape(|) is implementation-detail {*}
    multi method set-shape(Whatever) is raw {
        nqp::create(self.WHAT)
    }
    multi method set-shape(\shape) is raw {
        self.set-shape(shape.List)
    }
    multi method set-shape(List:D \shape) is raw {
        my int $dims = shape.elems;  # reifies
        my $reified := nqp::getattr(nqp::decont(shape),List,'$!reified');

        # just a list with Whatever, so no shape
        if nqp::iseq_i($dims,1)
          && nqp::istype(nqp::atpos($reified,0),Whatever) {
            nqp::create(self.WHAT)
        }

        # we haz dimensions
        elsif $dims {
            my $what := self.WHAT.^mixin(
              nqp::atpos(dim2role,nqp::isle_i($dims,3) && $dims)
            );
            $what.^set_name(self.^name)           # correct name if needed
              if nqp::isne_s($what.^name,self.^name);

            my $array := nqp::p6bindattrinvres(
              nqp::create($what),List,'$!reified',
              Rakudo::Internals.SHAPED-ARRAY-STORAGE(shape,nqp::knowhow,Mu)
            );
            nqp::p6bindattrinvres($array,$what,'$!shape',nqp::decont(shape))
        }

        # flatland
        else {
            X::NotEnoughDimensions.new(
              operation         => 'create',
              got-dimensions    => 0,
              needed-dimensions => '',
            ).throw
        }
    }

    my class LTHandle {
        has Mu $!reified;
        has Mu $!todo;
        has Mu $!descriptor;
    }

    method TEMP-LET-LOCALIZE() is raw is implementation-detail {
        my \handle = nqp::create(LTHandle);
        nqp::bindattr(handle, LTHandle, '$!reified', nqp::getattr(self, List, '$!reified'));
        nqp::bindattr(handle, LTHandle, '$!todo', nqp::getattr(self, List, '$!todo'));
        nqp::bindattr(handle, LTHandle, '$!descriptor', nqp::getattr(self, Array, '$!descriptor'));
        self.STORE: self.clone;
        handle
    }

    method TEMP-LET-RESTORE(\handle --> Nil) is implementation-detail {
        nqp::bindattr(self, List, '$!reified', nqp::getattr(handle, LTHandle, '$!reified'));
        nqp::bindattr(self, List, '$!todo', nqp::getattr(handle, LTHandle, '$!todo'));
        nqp::bindattr(self, Array, '$!descriptor', nqp::getattr(handle, LTHandle, '$!descriptor'));
    }

    proto method is-generic {*}
    multi method is-generic(::?CLASS:U:) { False }
    multi method is-generic(::?CLASS:D:) { nqp::hllbool($!descriptor.is_generic) }

    multi method INSTANTIATE-GENERIC(::?CLASS:D: TypeEnv:D \type-environment) is raw {
        $!descriptor.is_generic
            ??  nqp::p6bindattrinvres(
                    self.clone, Array, '$!descriptor', type-environment.instantiate($!descriptor) )
            !! self.clone
    }

    method ^parameterize(Mu:U \arr, Mu \of) {
        if nqp::isconcrete(of) {
            die "Can not parameterize {arr.^name} with {of.raku}"
        }
        else {
            my $what := arr.^mixin(Array::Typed[of]);
            # needs to be done in COMPOSE phaser when that works
            $what.^set_name("{arr.^name}[{of.^name}]");
            $what
        }
    }
}

# vim: expandtab shiftwidth=4
