]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++, contracts: Apply P200R14 constification.
authorNina Ranns <dinka.ranns@gmail.com>
Tue, 14 Oct 2025 11:37:48 +0000 (12:37 +0100)
committerIain Sandoe <iain@sandoe.co.uk>
Wed, 28 Jan 2026 01:24:51 +0000 (01:24 +0000)
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 <iain@sandoe.co.uk>
Co-authored-by: Iain Sandoe <iain@sandoe.co.uk>
gcc/cp/contracts.cc
gcc/cp/contracts.h
gcc/cp/cp-tree.h
gcc/cp/lambda.cc
gcc/cp/parser.cc
gcc/cp/pt.cc
gcc/cp/semantics.cc
gcc/tree-core.h
gcc/tree.h

index a98f9a68e46dc04d33272ab0871f249ff5b0f2b4..cae1be9d991f810952f736e7c4b454184ea4bb42 100644 (file)
@@ -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)
index 67ba65a1752d365acded9345d291c6ca2171b51f..ba5a203321b286549a8007e2013374c66e63b6a6 100644 (file)
@@ -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);
 
index ef66390568074bd95086bd5a5914ed8a339f7fe1..9153ff5b43d62c981888c6cacb374f9e278b1a27 100644 (file)
@@ -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)
index 6bec288d4c580fd7bd18fe2227469542d31ac3ea..402d5f7110a05d17ffd598cfe41fafbd6b71b0b8 100644 (file)
@@ -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);
index a8d373ea6b77a4b986c5c7cc6a95e94ccfa55d56..6e310f2c0fdea48161bcd91d54d4be3cd27d4377 100644 (file)
@@ -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;
index 60392f1a089c4b6af2ff9611167bb044c72a75be..614b006069dd1294a4a2cea45b6ea3e006deb535 100644 (file)
@@ -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
index 9191472c7b12e5c2951e3d933888b930a700c3a7..0a15b32a16d2d3ae4d41b8ef55005d954cf1f3bb 100644 (file)
@@ -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
index 4e158336c6ca013b5861afac3d5b80cf64920ab8..07e9318f5e8b88cc3a03bdf5a9cab3a372b55856 100644 (file)
@@ -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
index b4734172f6b108b9bca2f855aa07c7214493d9fc..dcd3ff85d04f388766063f3ca6882e7daf856993 100644 (file)
@@ -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