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)
$