The = operator itself is always item assignment level, which is tighter than comma. However, it may apply a "sub-precedence", which is how it is compared to any further infixes in the expression that follow it. The term that was parsed prior to the = is considered, and:
- In the case that the assignment is to a
Scalar variable, then the = operator works just like any other item assignment precedence operator, which is tighter than the precedence of ,
- In any other case, its precedence relative to following infixes is list prefix, which is looser than the precedence of
,
To consider some cases (first, where it doesn't impact anything):
$foo = 42; # $ sigil, so item assignment precedence after
@foo = 1; # @ sigil, so list assignment precedence after
@foo[0] = 1; # postcircumfix (indexing operator) means list assignment after...
$foo[0] = 1; # ...always, even in this case
If we have a single variable on the left and a list on the right, then:
@foo = 1, 2; # List assignment into @foo two values
$foo = 1, 2; # Assignment of 1 into $foo, 2 is dead code
These apply with the = initializer (following a my $var declaration). This means that:
loop (my $i = 0, my $j = $end; $i < $end; $i++, $j--) {
...
}
Will result in $i being assigned 0 and $j being assigned $end.
Effectively, the rule means we get to have parentheses-free initialization of array and hash variables, while still having lists of scalar initializations work out as in the loop case.
Turning to the examples in the question. First, this:
($a, $b) = test();
Parses a single term, then encounters the =. The precedence when comparing any following infixes would be list prefix (looser than ,). However, there are no more infixes here, so it doesn't really matter.
In this case:
sub test() { return 1, 2 }
my ($a, $b);
$a, $b = test();
The precedence parser sees the , infix, followed by the = infix. The = infix in itself is tighter than comma (item assignment precedence); the sub-precedence is only visible to infixes parsed after the = (and there are none here).
Note that were it not this way, and the precedence shift applied to the expression as a whole, then:
loop (my $i = 0, my @lagged = Nil, |@values; $i < @values; $i++) {
...
}
Would end up grouped not as (my $i = 0), (my @lagged = Nil, |@values), but rather (my $i = 0, my @lagged) = Nil, |@values, which is rather less useful.