On Mon, Jul 06, 2026 at 01:33:46PM +0300, Vladislav Semykin wrote:
> Made changes according to Jason's Merrill comments.

> From 9a42e71cd2244b80327cb37fe372c45b018b5958 Mon Sep 17 00:00:00 2001
> From: Vladislav Semykin <[email protected]>
> Date: Mon, 6 Jul 2026 13:25:26 +0300
> Subject: [PATCH v4] cp: fix unevaluated operand context for typeid [PR
>  c++/125886]
> 
> Per [expr.typeid]/4-5, a typeid operand is unevaluated by default and
> is evaluated only for a glvalue of polymorphic class type whose dynamic
> type is not known at compile time.  Previously GCC always parsed the
> operand in an evaluated context, which broke unevaluated uses (declval,
> non-static data members per DR613, function parameters) and missed
> lambda capture diagnostics for evaluated polymorphic operands.
> 
> Implement a two-pass parse: first under cp_unevaluated, then - if
> typeid_evaluated_p says the operand is evaluated - roll back and
> re-parse under cp_evaluated.  Share the evaluated/unevaluated predicate
> via typeid_evaluated_p in rtti.cc, used from the parser, tsubst_expr,
> and build_typeid.
> 
> Also fixes PR c++/68604 and PR c++/116385, and removes a now-stale xfail
> in g++.dg/coroutines/unevaluated.C.
> 
> Changes since v1:
> - Apply the same two-pass logic to template-dependent operands in
>   tsubst_expr (TYPEID_EXPR), as Jason requested.
> - Fix lambda capture: a polymorphic glvalue typeid operand inside a
>   lambda without a capture-default is now correctly diagnosed as
>   "not captured".  Root cause was typeid_evaluated_p rejecting
>   reference types and being gated on the "nonnull" flag, which is
>   unrelated to the evaluated/unevaluated decision.
> - Factor the polymorphic-glvalue predicate into shared typeid_evaluated_p
>   used by the parser, tsubst, and build_typeid.
> - Drop the cxx_dialect >= cxx11 gate: per Jason, DR613 applies in all
>   modes, since finish_non_static_data_member doesn't check cxx_dialect
>   either.
> 
> Changes since v2:
> - Restored the full commit message (rationale + ChangeLog entries).
> - Shortened cp_unevaluated_operand comments in cp-tree.h and parser.cc
>   (git gcc-style).
> - Fixed indentation in parser.cc and rtti.cc (git gcc-style).
> - Use cp_unevaluated RAII in cp_parser_postfix_expression instead of
>   manual ++/--cp_unevaluated_operand, matching tsubst_expr.
> - Kept glvalue_p in typeid_evaluated_p: [expr.typeid]/4 applies only to
>   a glvalue of polymorphic class type; a polymorphic class prvalue can
>   have resolves_to_fixed_type_p false yet still be unevaluated (see
>   "Not a glvalue" in g++.dg/cpp2a/constexpr-typeid2.C).  Without
>   glvalue_p that case would incorrectly take the evaluated re-parse
>   path.
> - Investigated the g++.dg/modules/compile-std1.C failure Jason flagged
>   as surprising (reproduced with
>   'make check-c++ RUNTESTFLAGS="modules.exp=compile-std1.C"').
>   g++.log shows cc1plus: fatal error: bits/std.cc: No such file or directory
>   and the same for bits/std.compat.cc under --compile-std-module; import
>   std then fails because gcm.cache/std.gcm was never built.  Here the
>   libstdc++ symlinks at .../include/bits/std.{,compat.}cc point at
>   src/c++23/std.{,compat.}cc, but those generated files are missing
>   (only .in templates remain) - incomplete libstdc++ modules build, not
>   a cp/typeid regression.  Unrelated to this patch.
> 
> Changes since v3:
> - Removed cp_evaluated on the re-parse path in both
>   cp_parser_postfix_expression and tsubst_expr.  As Jason pointed
>   out, the re-parse should not override the typeid itself being
>   within an outer unevaluated operand (e.g. sizeof (typeid (b)));
>   it should just drop back to whatever context enclosed it.
> - Added a comment on the glvalue_p check in typeid_evaluated_p
>   noting it's only needed because resolves_to_fixed_type_p doesn't
>   handle all prvalue cases (e.g. COMPOUND_EXPR), per Jason's
>   observation.
> - Removed a stale test (lambda_sizeof_typeid) that encoded the old,
>   incorrect behavior (requiring capture for typeid nested inside
>   sizeof); added nested_unevaluated and nested_unevaluated_lambda
>   to cover the corrected behavior instead.
> 
> Tested full CI on: x86_64 GNU/Linux 6.17.0-35-generic Ubuntu 24.04.
> The following failures are pre-existing on trunk without this patch
> and are unrelated to the changes here (full log via
> 'make -C gcc -k check-c++-all' attached):
> 
> FAIL: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11
>   at line 885 (test for warnings, line 884)
> XPASS: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11
>   PR analyzer/103546 (test for bogus messages, line 892)
> FAIL: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11
>   (test for excess errors)
> FAIL: g++.dg/guality/pr55665.C -O2 -flto -fno-use-linker-plugin
>   -flto-partition=none line 23 p == 40
> FAIL: g++.dg/modules/compile-std1.C -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/modules/xtreme-header-8.C -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/modules/xtreme-header-2_a.H -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/modules/xtreme-header-2_c.C -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/modules/xtreme-header-7_a.H -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/modules/xtreme-header_a.H -std=c++26 -fimplicit-constexpr
>   (test for excess errors)
> FAIL: g++.dg/plugin/std-module-exports-c++20.C
>   -fplugin=./std_module_exports_plugin.so (test for excess errors)
> FAIL: g++.dg/plugin/std-module-exports-c++23.C
>   -fplugin=./std_module_exports_plugin.so (test for excess errors)
> FAIL: g++.dg/plugin/std-module-exports-c++26.C
>   -fplugin=./std_module_exports_plugin.so (test for excess errors)
> 
> gcc/cp/ChangeLog:
>       PR c++/125886
>       * parser.cc (cp_parser_postfix_expression): Two-pass typeid parse.
>       * pt.cc (tsubst_expr): Same for TYPEID_EXPR.
>       * rtti.cc (typeid_evaluated_p, build_typeid): Shared predicate.
>       * cp-tree.h: Declare typeid_evaluated_p.
> 
> gcc/testsuite/ChangeLog:
>       * g++.dg/cpp0x/pr125886.C: New test.
>       * g++.dg/rtti/typeid14.C, g++.dg/rtti/typeid15.C: New tests.
>       * g++.dg/coroutines/unevaluated.C: Drop stale xfail.
> 
> Signed-off-by: Vladislav Semykin <[email protected]>
> ---
>  gcc/cp/cp-tree.h                              |  1 +
>  gcc/cp/parser.cc                              | 26 +++++-
>  gcc/cp/pt.cc                                  | 17 +++-
>  gcc/cp/rtti.cc                                | 48 ++++++++---
>  gcc/testsuite/g++.dg/coroutines/unevaluated.C |  3 +-
>  gcc/testsuite/g++.dg/cpp0x/pr125886.C         | 82 +++++++++++++++++++
>  gcc/testsuite/g++.dg/rtti/typeid14.C          | 19 +++++
>  gcc/testsuite/g++.dg/rtti/typeid15.C          | 23 ++++++
>  8 files changed, 201 insertions(+), 18 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886.C
>  create mode 100644 gcc/testsuite/g++.dg/rtti/typeid14.C
>  create mode 100644 gcc/testsuite/g++.dg/rtti/typeid15.C
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 132139f9d..df0f253cc 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7486,6 +7486,7 @@ extern tree current_nonlambda_class_type        (void);
>  extern tree finish_struct                    (tree, tree);
>  extern void finish_struct_1                  (tree);
>  extern int resolves_to_fixed_type_p          (tree, int * = NULL);
> +extern bool typeid_evaluated_p (tree);
>  extern void init_class_processing            (void);
>  extern int is_empty_class                    (tree);
>  extern bool is_really_empty_class            (tree, bool);
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 8194106c6..5bb434903 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -292,7 +292,8 @@ static void missing_template_diag
>  static FILE *cp_lexer_debug_stream;
>  
>  /* Nonzero if we are parsing an unevaluated operand: an operand to
> -   sizeof, typeof, or alignof.  */
> +   sizeof, typeof, or alignof.  This is a count since operands to
> +   sizeof can be nested.  */
>  int cp_unevaluated_operand;
>  
>  /* Nonzero if we are parsing a reflect-expression and shouldn't strip
> @@ -8570,9 +8571,28 @@ cp_parser_postfix_expression (cp_parser *parser, bool 
> address_p, bool cast_p,
>       else
>         {
>           tree expression;
> +         /* [expr.typeid]/4-5: parse the operand unevaluated first; if it is
> +            a polymorphic glvalue, roll back and re-parse it evaluated,
> +            since an evaluated parse has irreversible side-effects
> +            (mark_used -> instantiation; lambda capture).  */
> +         cp_lexer_save_tokens (parser->lexer);
> +         {
> +           cp_unevaluated u;
> +           expression = cp_parser_expression (parser, &idk);
> +         }
> +         if (expression != error_mark_node
> +             && processing_template_decl == 0
> +             && typeid_evaluated_p (expression))

If we can already be in an unevaluated context, I would think this
should check !cp_unevaluated_operand otherwise we'll parse in an
unevaluated context again.


Marek

Reply via email to