https://gcc.gnu.org/g:080b1cb5e4ad4af39c3a9e081dd8c382c0954cf6

commit r16-7088-g080b1cb5e4ad4af39c3a9e081dd8c382c0954cf6
Author: Nina Ranns <[email protected]>
Date:   Tue Oct 14 12:37:48 2025 +0100

    c++, contracts: Apply P200R14 constification.
    
    Split from the main patch as it was potentially contentious and might
    have been altered by WG21 NB comment resolution. However, it most likely
    makes sense to review in isolation (although we would expect to apply it
    squashed into the base patch).
    
    gcc/cp/ChangeLog:
    
            * contracts.cc (view_as_const, constify_contract_access,
            set_parm_used_in_post, check_param_in_postcondition,
            parm_used_in_post_p, check_postconditions_in_redecl): New.
            (check_redecl_contract): Handle constification.
            * contracts.h (constify_contract_access, view_as_const,
            contract_const_wrapper_p, strip_contract_const_wrapper): New.
            * cp-tree.h: Update tree flag usage comment.
            * lambda.cc (build_capture_proxy): Handle constification.
            * parser.cc (cp_parser_late_contract_condition, 
cp_parser_contract_assert,
            cp_parser_function_contract_specifier): Likewise.
            * pt.cc (tsubst_function_decl, tsubst_expr): Likewise.
            * semantics.cc (finish_id_expression_1, finish_decltype_type): 
Likewise.
    
    gcc/ChangeLog:
    
            * tree.h (CONST_WRAPPER_P): New.
    
    gcc/ChangeLog:
    
            * tree-core.h (struct tree_base): Update tree flag usage comment.
    
    Signed-off-by: Iain Sandoe <[email protected]>
    Co-authored-by: Iain Sandoe <[email protected]>

Diff:
---
 gcc/cp/contracts.cc | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 gcc/cp/contracts.h  |  25 +++++++++++
 gcc/cp/cp-tree.h    |   1 +
 gcc/cp/lambda.cc    |   3 ++
 gcc/cp/parser.cc    |  24 ++++++++++
 gcc/cp/pt.cc        |   8 ++++
 gcc/cp/semantics.cc |  14 ++++++
 gcc/tree-core.h     |   3 ++
 gcc/tree.h          |   5 +++
 9 files changed, 209 insertions(+), 1 deletion(-)

diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc
index a98f9a68e46d..cae1be9d991f 100644
--- a/gcc/cp/contracts.cc
+++ b/gcc/cp/contracts.cc
@@ -476,6 +476,128 @@ finish_contract_condition (cp_expr condition)
   return condition_conversion (condition);
 }
 
+/* Wrap the DECL into VIEW_CONVERT_EXPR representing const qualified version
+   of the declaration.  */
+
+tree
+view_as_const (tree decl)
+{
+  if (!contract_const_wrapper_p (decl))
+    {
+      tree ctype = TREE_TYPE (decl);
+      location_t loc =
+         EXPR_P (decl) ? EXPR_LOCATION (decl) : DECL_SOURCE_LOCATION (decl);
+      ctype = cp_build_qualified_type (ctype, (cp_type_quals (ctype)
+                                              | TYPE_QUAL_CONST));
+      decl = build1 (VIEW_CONVERT_EXPR, ctype, decl);
+      SET_EXPR_LOCATION (decl, loc);
+      /* Mark the VCE as contract const wrapper.  */
+      CONST_WRAPPER_P (decl) = true;
+    }
+  return decl;
+}
+
+/* Constify access to DECL from within the contract condition.  */
+
+tree
+constify_contract_access (tree decl)
+{
+  /* We check if we have a variable, a parameter, a variable of reference type,
+   * or a parameter of reference type
+   */
+  if (!TREE_READONLY (decl)
+      && (VAR_P (decl)
+         || (TREE_CODE (decl) == PARM_DECL)
+         || (REFERENCE_REF_P (decl)
+             && (VAR_P (TREE_OPERAND (decl, 0))
+                 || (TREE_CODE (TREE_OPERAND (decl, 0)) == PARM_DECL)
+                 || (TREE_CODE (TREE_OPERAND (decl, 0))
+                     == TEMPLATE_PARM_INDEX)))))
+    decl = view_as_const (decl);
+
+  return decl;
+}
+
+/* Indicate that PARM_DECL DECL is ODR used in a postcondition.  */
+
+static void
+set_parm_used_in_post (tree decl, bool constify = true)
+{
+  gcc_checking_assert (TREE_CODE (decl) == PARM_DECL);
+  DECL_LANG_FLAG_4 (decl) = constify;
+}
+
+/* Test if PARM_DECL is ODR used in a postcondition.  */
+
+static bool
+parm_used_in_post_p (const_tree decl)
+{
+  /* Check if this parameter is odr used within a function's postcondition  */
+  return ((TREE_CODE (decl) == PARM_DECL) && DECL_LANG_FLAG_4 (decl));
+}
+
+/* If declaration DECL is a PARM_DECL and it appears in a postcondition, then
+   check that it is not a non-const by-value param. LOCATION is where the
+   expression was found and is used for diagnostic purposes.  */
+
+void
+check_param_in_postcondition (tree decl, location_t location)
+{
+  if (processing_postcondition
+      && TREE_CODE (decl) == PARM_DECL
+      /* TREE_CODE (decl) == PARM_DECL only holds for non-reference
+        parameters.  */
+      && !cp_unevaluated_operand
+      /* Return value parameter has DECL_ARTIFICIAL flag set. The flag
+        presence of the flag should be sufficient to distinguish the
+        return value parameter in this context.  */
+      && !(DECL_ARTIFICIAL (decl)))
+    {
+      set_parm_used_in_post (decl);
+
+      if (!dependent_type_p (TREE_TYPE (decl))
+         && !CP_TYPE_CONST_P (TREE_TYPE (decl)))
+       {
+         error_at (location,
+                   "a value parameter used in a postcondition must be const");
+         inform (DECL_SOURCE_LOCATION (decl), "parameter declared here");
+       }
+    }
+}
+
+/* Check if parameters used in postconditions are const qualified on
+   a redeclaration that does not specify contracts or on an instantiation
+   of a function template.  */
+
+void
+check_postconditions_in_redecl (tree olddecl, tree newdecl)
+{
+  tree contract_spec = get_fn_contract_specifiers (olddecl);
+  if (!contract_spec)
+    return;
+
+  tree t1 = FUNCTION_FIRST_USER_PARM (olddecl);
+  tree t2 = FUNCTION_FIRST_USER_PARM (newdecl);
+
+  for (; t1 && t1 != void_list_node;
+  t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2))
+    {
+      if (parm_used_in_post_p (t1))
+       {
+         set_parm_used_in_post (t2);
+         if (!dependent_type_p (TREE_TYPE (t2))
+             && !CP_TYPE_CONST_P (TREE_TYPE (t2))
+             && !TREE_READONLY (t2))
+           {
+             error_at (DECL_SOURCE_LOCATION (t2),
+             "value parameter %qE used in a postcondition must be const", t2);
+             inform (DECL_SOURCE_LOCATION (olddecl),
+             "previous declaration here");
+           }
+       }
+    }
+}
+
 void
 maybe_update_postconditions (tree fndecl)
 {
@@ -1013,7 +1135,10 @@ check_redecl_contract (tree newdecl, tree olddecl)
     }
 
   if (old_contracts && !new_contracts)
-    return;
+    /* We allow re-declarations to omit contracts declared on the initial decl.
+       In fact, this is required if the conditions contain lambdas.  Check if
+       all the parameters are correctly const qualified. */
+    check_postconditions_in_redecl (olddecl, newdecl);
   else if (old_contracts && new_contracts
           && !contract_any_deferred_p (old_contracts)
           && contract_any_deferred_p (new_contracts)
diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h
index 67ba65a1752d..ba5a203321b2 100644
--- a/gcc/cp/contracts.h
+++ b/gcc/cp/contracts.h
@@ -131,6 +131,8 @@ extern void update_late_contract            (tree, tree, 
cp_expr);
 extern void check_redecl_contract              (tree, tree);
 extern tree invalidate_contract                        (tree);
 extern tree copy_and_remap_contracts           (tree, tree);
+extern tree constify_contract_access           (tree);
+extern tree view_as_const                      (tree);
 
 extern void set_fn_contract_specifiers         (tree, tree);
 extern void update_fn_contract_specifiers      (tree, tree);
@@ -162,6 +164,29 @@ set_decl_contracts (tree decl, tree contract_attrs)
   set_fn_contract_specifiers (decl, contract_attrs);
 }
 
+/* Test if EXP is a contract const wrapper node.  */
+
+inline bool
+contract_const_wrapper_p (const_tree exp)
+{
+  /* A wrapper node has code VIEW_CONVERT_EXPR, and the flag base.private_flag
+     is set. The wrapper node is used to used to constify entities inside
+     contract assertions.  */
+  return ((TREE_CODE (exp) == VIEW_CONVERT_EXPR) && CONST_WRAPPER_P (exp));
+}
+
+/* If EXP is a contract_const_wrapper_p, return the wrapped expression.
+   Otherwise, do nothing. */
+
+inline tree
+strip_contract_const_wrapper (tree exp)
+{
+  if (contract_const_wrapper_p (exp))
+    return TREE_OPERAND (exp, 0);
+  else
+    return exp;
+}
+
 /* TODO : decide if we should push the tests into contracts.cc  */
 extern contract_evaluation_semantic get_evaluation_semantic (const_tree);
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index ef6639056807..9153ff5b43d6 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -582,6 +582,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       DECL_SELF_REFERENCE_P (in a TYPE_DECL)
       DECL_INVALID_OVERRIDER_P (in a FUNCTION_DECL)
       DECL_UNINSTANIATED_TEMPLATE_FRIEND_P (in TEMPLATE_DECL)
+      parm_used_in_post_p (in PARM_DECL)
    5: DECL_INTERFACE_KNOWN.
    6: DECL_THIS_STATIC (in VAR_DECL, FUNCTION_DECL or PARM_DECL)
       DECL_FIELD_IS_BASE (in FIELD_DECL)
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index 6bec288d4c58..402d5f7110a0 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "target.h"
 #include "decl.h"
 #include "flags.h"
+#include "contracts.h"
 
 /* Constructor for a lambda expression.  */
 
@@ -497,6 +498,8 @@ build_capture_proxy (tree member, tree init, bool early_p)
            init = PACK_EXPANSION_PATTERN (init);
        }
 
+      init = strip_contract_const_wrapper (init);
+
       if (INDIRECT_REF_P (init))
        init = TREE_OPERAND (init, 0);
       STRIP_NOPS (init);
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index a8d373ea6b77..6e310f2c0fde 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -33466,6 +33466,12 @@ cp_parser_late_contract_condition (cp_parser *parser, 
tree fn, tree contract)
   cp_token_cache *tokens = DEFPARSE_TOKENS (condition);
   cp_parser_push_lexer_for_tokens (parser, tokens);
 
+  /* If we have a current class object, we need to consider
+     it const when processing the contract condition.  */
+  tree current_class_ref_copy = current_class_ref;
+  if (flag_contracts && current_class_ref_copy)
+    current_class_ref = view_as_const (current_class_ref_copy);
+
   /* Parse the condition, ensuring that parameters or the return variable
      aren't flagged for use outside the body of a function.  */
   begin_scope (sk_contract, fn);
@@ -33568,6 +33574,12 @@ cp_parser_contract_assert (cp_parser *parser, cp_token 
*token)
   /* Enable location wrappers when parsing contracts.  */
   auto suppression = make_temp_override (suppress_location_wrappers, 0);
 
+  /* If we have a current class object, see if we need to consider
+     it const when processing the contract condition.  */
+  tree current_class_ref_copy = current_class_ref;
+  if (current_class_ref_copy)
+    current_class_ref = view_as_const (current_class_ref_copy);
+
   /* Parse the condition.  */
   begin_scope (sk_contract, current_function_decl);
   bool old_pc = processing_postcondition;
@@ -33581,6 +33593,9 @@ cp_parser_contract_assert (cp_parser *parser, cp_token 
*token)
   processing_postcondition = old_pc;
   pop_bindings_and_leave_scope ();
 
+  /* Revert (any) constification of the current class object.  */
+  current_class_ref = current_class_ref_copy;
+
   parens.require_close (parser);
 
   if (!contract || contract == error_mark_node)
@@ -33725,6 +33740,12 @@ cp_parser_function_contract_specifier (cp_parser 
*parser)
       /* Enable location wrappers when parsing contracts.  */
       auto suppression = make_temp_override (suppress_location_wrappers, 0);
 
+      /* If we have a current class object, see if we need to consider
+       it const when processing the contract condition.  */
+      tree current_class_ref_copy = current_class_ref;
+      if (current_class_ref_copy)
+      current_class_ref = view_as_const (current_class_ref_copy);
+
       /* Parse the condition, ensuring that parameters or the return variable
        aren't flagged for use outside the body of a function.  */
       begin_scope (sk_contract, current_function_decl);
@@ -33748,6 +33769,9 @@ cp_parser_function_contract_specifier (cp_parser 
*parser)
                           && scope_chain->bindings->kind == sk_contract);
       pop_bindings_and_leave_scope ();
 
+      /* Revert (any) constification of the current class object.  */
+      current_class_ref = current_class_ref_copy;
+
       if (contract != error_mark_node)
        {
          location_t end = cp_lexer_peek_token (parser->lexer)->location;
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 60392f1a089c..614b006069dd 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -15270,6 +15270,9 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t 
complain,
   if (tree ctrct = get_fn_contract_specifiers (t))
     set_fn_contract_specifiers (r, ctrct);
 
+  /* The parms have now been substituted, check for incorrect const cases.  */
+  check_postconditions_in_redecl (t, r);
+
   if (DECL_FRIEND_CONTEXT (t))
     SET_DECL_FRIEND_CONTEXT (r,
                             tsubst (DECL_FRIEND_CONTEXT (t),
@@ -23037,6 +23040,11 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
          /* force_paren_expr can also create a VIEW_CONVERT_EXPR.  */
          RETURN (finish_parenthesized_expr (op));
 
+       check_param_in_postcondition (op, EXPR_LOCATION (t));
+
+       if (flag_contracts && processing_contract_condition)
+           op = constify_contract_access (op);
+
        /* Otherwise, we're dealing with a wrapper to make a C++20 template
           parameter object const.  */
        if (TREE_TYPE (op) == NULL_TREE
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 9191472c7b12..0a15b32a16d2 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4886,6 +4886,10 @@ finish_id_expression_1 (tree id_expression,
                   "integral or enumeration type", decl, TREE_TYPE (decl));
          *non_integral_constant_expression_p = true;
        }
+
+      if (flag_contracts && processing_contract_condition)
+       r = constify_contract_access (r);
+
       return r;
     }
   else if (TREE_CODE (decl) == UNBOUND_CLASS_TEMPLATE)
@@ -5083,6 +5087,10 @@ finish_id_expression_1 (tree id_expression,
        }
     }
 
+  check_param_in_postcondition (decl, location);
+  if (flag_contracts && processing_contract_condition)
+    decl = constify_contract_access (decl);
+
   return cp_expr (decl, location);
 }
 
@@ -13054,6 +13062,12 @@ finish_decltype_type (tree expr, bool 
id_expression_or_member_access_p,
       if (identifier_p (expr))
         expr = lookup_name (expr);
 
+      /* If e is a constified expression inside a contract assertion,
+        strip the const wrapper. Per P2900R14, "For a function f with the
+        return type T , the result name is an lvalue of type const T , 
decltype(r)
+        is T , and decltype((r)) is const T&."  */
+      expr = strip_contract_const_wrapper (expr);
+
       if (INDIRECT_REF_P (expr)
          || TREE_CODE (expr) == VIEW_CONVERT_EXPR)
         /* This can happen when the expression is, e.g., "a.b". Just
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 4e158336c6ca..07e9318f5e8b 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1387,6 +1387,9 @@ struct GTY(()) tree_base {
        ENUM_IS_OPAQUE in
           ENUMERAL_TYPE
 
+       CONST_WRAPPER_P in
+          VIEW_CONVERT_EXPR (used by C++)
+
    protected_flag:
 
        TREE_PROTECTED in
diff --git a/gcc/tree.h b/gcc/tree.h
index b4734172f6b1..dcd3ff85d04f 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -796,6 +796,11 @@ extern void omp_clause_range_check_failed (const_tree, 
const char *, int,
 /* Determines whether an ENUMERAL_TYPE has defined the list of constants. */
 #define ENUM_IS_OPAQUE(NODE) (ENUMERAL_TYPE_CHECK (NODE)->base.private_flag)
 
+/* Determines whether a VIEW_CONVERT_EXPR node is used to create const
+   qualified variant of its first operand (used by C++ contracts).  */
+#define CONST_WRAPPER_P(NODE) \
+  (TREE_CHECK (NODE, VIEW_CONVERT_EXPR)->base.private_flag)
+
 /* In an expr node (usually a conversion) this means the node was made
    implicitly and should not lead to any sort of warning.  In a decl node,
    warnings concerning the decl should be suppressed.  This is used at

Reply via email to