There appears to be a fundamental design problem in Parrot's
current implementation of :outer.  The short summary is that
:outer("sub_name") doesn't provide sufficient specificity
to accurately resolve an outer sub.

In particular, given:

    .namespace ['A']
    .sub 'outer'
        ...
    .end

    .namespace ['B']
    .sub 'outer'
        ...
        .lex '$a', x
        'inner'()
        ...
    .end

    .sub 'inner' :outer('outer')
        $P0 = find_lex '$a'
    .end

Parrot incorrectly uses A::outer as the :outer sub of B::inner.
In fact, the :outer flag seems to always use the first sub
that it finds having a matching name.  At the bottom of this
message I've added a fuller description and demonstration of
the problem.

Jonathan and I discussed this briefly on #parrot.
Constraining :outer to subs only in the same namespace
isn't a sufficient solution for at least two reasons:

1.  Some languages (incl Perl 6) allow inner classes and inner
    namespaces that can access items in (outer) lexical scopes.

2.  Some subs can be :multi -- i.e., multiple subs (each of which
    may be an independent outer scope) may be referenced by the
    same global symbol name.

The best solution I've come up with thus far is to allow every sub to
have a :lexid("...") attribute that uniquely identifies the sub.
The :outer() flag would then refer to this lexid instead of the 
sub's name.  A sub that doesn't supply a :lexid("...") attribute 
would use its normal name as its lexid (thus existing simple cases
of :outer continue to work).

An alternate approach would be go the other way -- have every sub
use a unique name, and use an :export("xyz") flag to cause the
sub to be placed in the namespace under its common name.  The
:anon flag would continue to mean "don't make an entry in the
namespace", and omitting :export() would continue to use the sub's
name as the exported name.  (I choose :export here to parallel the 
proposal in RT#53302 regarding listing methods in namespaces, 
but any flag name would work for me.)

Yet another approach would be to keep things as they are now,
but have :outer only refer to the closest (most recent) version
of a sub with that name.  We still may have to be careful about
dealing with :multi subs, though, and it might be possible to craft
some HLL code where it's not possible to make this approach work.

(A fourth approach, which I have a strong dislike for, is to have
PCT always generate a unique name for every sub and then use a
:load :init sub to bind them as their common names in the namespace.)

Lexical symbol handling in Parrot is rapidly becoming a huge blocker 
for progress on Rakudo -- there are a number of cases in the test 
suite that have nested blocks and subs that can't really be implemented
in Rakudo due to problems with Parrot's lexicals.

Pm

-----fuller description-----

Here's a longish test program that demonstrates the problem.
The key thing to note is that we have two subs named 'bar',
albeit in different namespaces.  The Foo::inner sub wants
Foo::bar to be its :outer lexical scope.

  $ cat x.pir
  .sub 'main' :main
      'bar'()
  
      $P0 = get_hll_global ['Foo'], 'bar'
      $P0('hello world')
  .end
  
  .sub 'printf'
      .param string fmt
      .param pmc args :slurpy
      $S0 = sprintf fmt, args
      print $S0
      .return ()
  .end
  
  
  .sub 'bar'
      $P0 = get_global 'bar'
      $I0 = get_addr $P0
      'printf'("in global 'bar' (0x%x)\n", $I0)
  .end
  
  
  .namespace ['Foo']
  .sub 'bar'
      .param pmc x
      .lex '$a', x
  
      $P0 = get_global 'bar'
      $I0 = get_addr $P0
      'printf'("in Foo::bar (0x%x)\n", $I0)
  
      'inner'()
      'printf'("back in Foo::bar (0x%x)\n", $I0)
  .end
  
  
  .sub 'inner' :outer('bar')
      $P0 = get_global 'inner'
      $I0 = get_addr $P0
      'printf'("in Foo::inner (0x%x)\n", $I0)
  
      $P0 = getinterp
      $P1 = $P0['outer']
      $I1 = get_addr $P1
      'printf'("Foo::inner's :outer is 0x%x\n", $I1)
  
      $P0 = find_lex '$a'
      say $P0
      .return ()
  .end

When the above is run, we can see that Foo::inner incorrectly
receives the global 'bar' sub as its outer scope.  As a result,
it's unable to find the lexical '$a' that was set by Foo::bar .

  $ ./parrot x.pir
  in global 'bar' (0x82484b8)
  in Foo::bar (0x8248508)
  in Foo::inner (0x82485b4)
  Foo::inner's :outer is 0x82484b8
  Lexical '$a' not found
  current instr.: 'parrot;Foo;inner' pc 136 (x.pir:48)
  called from Sub 'parrot;Foo;bar' pc 83 (x.pir:33)
  called from Sub 'main' pc 18 (x.pir:5)

If we change the name of the global 'bar' to something like
'bar2', then Foo::inner correctly attaches Foo::bar as its outer
sub and everything works fine:

  $ ./parrot y.pir
  in global 'bar2' (0x8248528)
  in Foo::bar (0x8248578)
  in Foo::inner (0x8248624)
  Foo::inner's :outer is 0x8248578
  hello world
  back in Foo::bar (0x8248578)
  $


Reply via email to