BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause

Started by Leendert Gravendeel2 days ago3 messagesbugs
Jump to latest
#1Leendert Gravendeel
leenderthenk@gmail.com

Hello,

I believe I have found a parser issue in PL/pgSQL involving the
FOREACH ... SLICE syntax.

Version:
PostgreSQL 18 (reproduced on current release)

Description:
When using FOREACH with the SLICE clause, a loop variable named
`slice` is misinterpreted as the SLICE keyword, causing the statement
to fail. Renaming the variable to anything else makes the function
work as expected.

Reproduction:

CREATE FUNCTION test_slice_conflict() RETURNS text
LANGUAGE plpgsql AS $$
DECLARE
slice integer[];
arr integer[] := ARRAY[[1,2],[3,4]];
BEGIN
FOREACH slice SLICE 1 IN ARRAY arr LOOP
END LOOP;
RETURN 'ok';
END;
$$;

Observed behavior:
The function fails to compile due to incorrect parsing of `slice`
as the SLICE keyword.

Expected behavior:
`slice` should be treated as a normal identifier (loop variable),
and the function should compile and run successfully.

Additional notes:
- Renaming the variable (e.g. to `abc`) makes the function work.
- This suggests a keyword/identifier ambiguity where the parser
prefers the SLICE keyword in this context.

Given that the documentation describes the FOREACH target as a
regular variable, this behavior seems unintended.

Please let me know if additional information is needed.

Best regards,
Leendert Gravendeel

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Leendert Gravendeel (#1)
Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause

Leendert Gravendeel <leenderthenk@gmail.com> writes:

When using FOREACH with the SLICE clause, a loop variable named
`slice` is misinterpreted as the SLICE keyword, causing the statement
to fail. Renaming the variable to anything else makes the function
work as expected.

So, um, don't do that. PL/pgSQL will let you use (many of) its keywords
as variable names, but once you declare such a variable, the word will
be taken as a variable reference not as a keyword for the rest of the
block. So what the parser is seeing here is

FOREACH variable variable 1 IN ARRAY variable LOOP

and it naturally doesn't like that. You could also break this
statement by making a variable named "array", for example.
(But not "foreach", "in", or "loop", as those are reserved words.)

Not sure how well this behavior is documented. I think there are
a small number of exceptions, in places where the scanner can know
that the next word must be a keyword, but most unreserved keywords
work this way. The only other thing we could do is make such keywords
fully reserved, which would break legacy code that uses "slice" or
whatever as a variable name without knowledge of the new SLICE syntax.
So we generally try to make newly-added keywords unreserved; this
ambiguity seems the lesser evil.

regards, tom lane

#3David G. Johnston
david.g.johnston@gmail.com
In reply to: Leendert Gravendeel (#1)
Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause

On Fri, Apr 17, 2026 at 7:33 AM Leendert Gravendeel <leenderthenk@gmail.com>
wrote:

I believe I have found a parser issue in PL/pgSQL involving the
FOREACH ... SLICE syntax.

Thanks for the report!

CREATE FUNCTION test_slice_conflict() RETURNS text
LANGUAGE plpgsql AS $$
DECLARE
slice integer[];
arr integer[] := ARRAY[[1,2],[3,4]];
BEGIN
FOREACH slice SLICE 1 IN ARRAY arr LOOP
END LOOP;
RETURN 'ok';
END;
$$;

Observed behavior:
The function fails to compile due to incorrect parsing of `slice`
as the SLICE keyword.

Expected behavior:
`slice` should be treated as a normal identifier (loop variable),
and the function should compile and run successfully.

Confirmed on master.

Chat provided much more context for how/why this happened and why this
seems like a good fix; prior art is my main argument though.

If a PL/pgSQL variable is named "slice", using it in a FOREACH ... SLICE
loop produces a spurious syntax error:

DO $$ DECLARE
slice integer[];
arr integer[] := ARRAY[[1,2],[3,4]];
BEGIN
FOREACH slice SLICE 1 IN ARRAY arr LOOP
END LOOP;
END; $$;
ERROR: syntax error at or near "SLICE"

The one-token lookahead in the for_variable grammar action runs under
normal identifier lookup, so when "slice" is in scope the following SLICE
keyword is consumed as a T_DATUM reference rather than K_SLICE, and
foreach_slice fails.

The fix is to suppress variable lookup for that lookahead, using the same
save/restore pattern already used in pl_gram.y::read_cursor_args():

/* Read the argument name, ignoring any matching variable */
save_IdentifierLookup = plpgsql_IdentifierLookup;
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
yylex(yylvalp, yyllocp, yyscanner);
argname = yylvalp->str;
plpgsql_IdentifierLookup = save_IdentifierLookup;

Therefore we need:

diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5009e59a78f..681fd3d5cff 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -1635,11 +1635,15 @@ for_variable : T_DATUM
                     else
                     {
                         int tok;
+ IdentifierLookup save_IdentifierLookup;
                         $$.scalar = $1.datum;
                         $$.row = NULL;
                         /* check for comma-separated list */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
                         tok = yylex(&yylval, &yylloc, yyscanner);
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
                         plpgsql_push_back_token(tok, &yylval, &yylloc,
yyscanner);
                         if (tok == ',')
                             $$.row = (PLpgSQL_datum *)

David J.