// Copyright (C) 2020-2023 Free Software Foundation, Inc. // This file is part of GCC. // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #include "rust-tree.h" #include "fold-const.h" #include "stringpool.h" #include "attribs.h" #include "escaped_string.h" namespace Rust { void mark_exp_read (tree exp) { if (exp == NULL) return; switch (TREE_CODE (exp)) { case VAR_DECL: gcc_fallthrough (); case PARM_DECL: DECL_READ_P (exp) = 1; break; case ARRAY_REF: case COMPONENT_REF: case MODIFY_EXPR: case REALPART_EXPR: case IMAGPART_EXPR: CASE_CONVERT: case ADDR_EXPR: case INDIRECT_REF: case FLOAT_EXPR: case NON_DEPENDENT_EXPR: case VIEW_CONVERT_EXPR: mark_exp_read (TREE_OPERAND (exp, 0)); break; case COMPOUND_EXPR: mark_exp_read (TREE_OPERAND (exp, 1)); break; case COND_EXPR: if (TREE_OPERAND (exp, 1)) mark_exp_read (TREE_OPERAND (exp, 1)); if (TREE_OPERAND (exp, 2)) mark_exp_read (TREE_OPERAND (exp, 2)); break; default: break; } } tree convert_from_reference (tree val) { if (TREE_TYPE (val) && TYPE_REF_P (TREE_TYPE (val))) { tree t = TREE_TYPE (TREE_TYPE (val)); tree ref = build1 (INDIRECT_REF, t, val); mark_exp_read (val); TREE_SIDE_EFFECTS (ref) = (TREE_THIS_VOLATILE (ref) || TREE_SIDE_EFFECTS (val)); val = ref; } return val; } tree mark_use (tree expr, bool rvalue_p, bool read_p, location_t loc /* = UNKNOWN_LOCATION */, bool reject_builtin /* = true */) { #define RECUR(t) mark_use ((t), rvalue_p, read_p, loc, reject_builtin) if (expr == NULL_TREE || error_operand_p (expr)) return expr; if (reject_builtin) return error_mark_node; if (read_p) mark_exp_read (expr); bool recurse_op[3] = {false, false, false}; switch (TREE_CODE (expr)) { case COMPONENT_REF: case NON_DEPENDENT_EXPR: recurse_op[0] = true; break; case COMPOUND_EXPR: recurse_op[1] = true; break; case COND_EXPR: recurse_op[2] = true; if (TREE_OPERAND (expr, 1)) recurse_op[1] = true; break; case INDIRECT_REF: if (REFERENCE_REF_P (expr)) { /* Try to look through the reference. */ tree ref = TREE_OPERAND (expr, 0); tree r = mark_rvalue_use (ref, loc, reject_builtin); if (r != ref) expr = convert_from_reference (r); } break; case VIEW_CONVERT_EXPR: if (location_wrapper_p (expr)) { loc = EXPR_LOCATION (expr); tree op = TREE_OPERAND (expr, 0); tree nop = RECUR (op); if (nop == error_mark_node) return error_mark_node; else if (op == nop) /* No change. */; else if (DECL_P (nop) || CONSTANT_CLASS_P (nop)) { /* Reuse the location wrapper. */ TREE_OPERAND (expr, 0) = nop; /* If we're replacing a DECL with a constant, we also need to change the TREE_CODE of the location wrapper. */ if (rvalue_p) TREE_SET_CODE (expr, NON_LVALUE_EXPR); } else { /* Drop the location wrapper. */ expr = nop; protected_set_expr_location (expr, loc); } return expr; } gcc_fallthrough (); CASE_CONVERT: recurse_op[0] = true; break; default: break; } for (int i = 0; i < 3; ++i) if (recurse_op[i]) { tree op = TREE_OPERAND (expr, i); op = RECUR (op); if (op == error_mark_node) return error_mark_node; TREE_OPERAND (expr, i) = op; } return expr; #undef RECUR } tree mark_rvalue_use (tree e, location_t loc /* = UNKNOWN_LOCATION */, bool reject_builtin /* = true */) { return mark_use (e, true, true, loc, reject_builtin); } tree mark_lvalue_use (tree expr) { return mark_use (expr, false, true, input_location, false); } tree mark_lvalue_use_nonread (tree expr) { return mark_use (expr, false, false, input_location, false); } tree mark_discarded_use (tree expr) { if (expr == NULL_TREE) return expr; STRIP_ANY_LOCATION_WRAPPER (expr); switch (TREE_CODE (expr)) { case COND_EXPR: TREE_OPERAND (expr, 2) = mark_discarded_use (TREE_OPERAND (expr, 2)); gcc_fallthrough (); case COMPOUND_EXPR: TREE_OPERAND (expr, 1) = mark_discarded_use (TREE_OPERAND (expr, 1)); return expr; case COMPONENT_REF: case ARRAY_REF: case INDIRECT_REF: case MEMBER_REF: break; default: if (DECL_P (expr)) break; else return expr; } return mark_use (expr, true, true, input_location, false); } tree convert_to_void (tree expr, impl_conv_void implicit) { location_t loc = expr_loc_or_input_loc (expr); if (expr == error_mark_node || TREE_TYPE (expr) == error_mark_node) return error_mark_node; expr = mark_discarded_use (expr); if (implicit == ICV_CAST) /* An explicit cast to void avoids all -Wunused-but-set* warnings. */ mark_exp_read (expr); if (!TREE_TYPE (expr)) return expr; if (VOID_TYPE_P (TREE_TYPE (expr))) return expr; switch (TREE_CODE (expr)) { case COND_EXPR: { /* The two parts of a cond expr might be separate lvalues. */ tree op1 = TREE_OPERAND (expr, 1); tree op2 = TREE_OPERAND (expr, 2); bool side_effects = ((op1 && TREE_SIDE_EFFECTS (op1)) || TREE_SIDE_EFFECTS (op2)); tree new_op1, new_op2; new_op1 = NULL_TREE; if (implicit != ICV_CAST && !side_effects) { if (op1) new_op1 = convert_to_void (op1, ICV_SECOND_OF_COND); new_op2 = convert_to_void (op2, ICV_THIRD_OF_COND); } else { if (op1) new_op1 = convert_to_void (op1, ICV_CAST); new_op2 = convert_to_void (op2, ICV_CAST); } expr = build3_loc (loc, COND_EXPR, TREE_TYPE (new_op2), TREE_OPERAND (expr, 0), new_op1, new_op2); break; } case COMPOUND_EXPR: { /* The second part of a compound expr contains the value. */ tree op1 = TREE_OPERAND (expr, 1); tree new_op1; if (implicit != ICV_CAST && !warning_suppressed_p (expr /* What warning? */)) new_op1 = convert_to_void (op1, ICV_RIGHT_OF_COMMA); else new_op1 = convert_to_void (op1, ICV_CAST); if (new_op1 != op1) { tree t = build2_loc (loc, COMPOUND_EXPR, TREE_TYPE (new_op1), TREE_OPERAND (expr, 0), new_op1); expr = t; } break; } case NON_LVALUE_EXPR: case NOP_EXPR: /* These have already decayed to rvalue. */ break; case CALL_EXPR: maybe_warn_nodiscard (expr, implicit); break; case INDIRECT_REF: { tree type = TREE_TYPE (expr); int is_reference = TYPE_REF_P (TREE_TYPE (TREE_OPERAND (expr, 0))); int is_volatile = TYPE_VOLATILE (type); int is_complete = COMPLETE_TYPE_P (type); /* Can't load the value if we don't know the type. */ if (is_volatile && !is_complete) { switch (implicit) { case ICV_CAST: warning_at (loc, 0, "conversion to void will not access " "object of incomplete type %qT", type); break; case ICV_SECOND_OF_COND: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in second operand " "of conditional expression", type); break; case ICV_THIRD_OF_COND: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in third operand " "of conditional expression", type); break; case ICV_RIGHT_OF_COMMA: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in right operand of " "comma operator", type); break; case ICV_LEFT_OF_COMMA: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in left operand of " "comma operator", type); break; case ICV_STATEMENT: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in statement", type); break; case ICV_THIRD_IN_FOR: warning_at (loc, 0, "indirection will not access object of " "incomplete type %qT in for increment " "expression", type); break; default: gcc_unreachable (); } } /* Don't load the value if this is an implicit dereference, or if the type needs to be handled by ctors/dtors. */ else if (is_volatile && is_reference) { switch (implicit) { case ICV_CAST: warning_at (loc, 0, "conversion to void will not access " "object of type %qT", type); break; case ICV_SECOND_OF_COND: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in second operand of " "conditional expression", type); break; case ICV_THIRD_OF_COND: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in third operand of " "conditional expression", type); break; case ICV_RIGHT_OF_COMMA: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in right operand of " "comma operator", type); break; case ICV_LEFT_OF_COMMA: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in left operand of comma " "operator", type); break; case ICV_STATEMENT: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in statement", type); break; case ICV_THIRD_IN_FOR: warning_at (loc, 0, "implicit dereference will not access " "object of type %qT in for increment expression", type); break; default: gcc_unreachable (); } } else if (is_volatile && TREE_ADDRESSABLE (type)) { switch (implicit) { case ICV_CAST: warning_at (loc, 0, "conversion to void will not access " "object of non-trivially-copyable type %qT", type); break; case ICV_SECOND_OF_COND: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in second " "operand of conditional expression", type); break; case ICV_THIRD_OF_COND: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in third " "operand of conditional expression", type); break; case ICV_RIGHT_OF_COMMA: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in right " "operand of comma operator", type); break; case ICV_LEFT_OF_COMMA: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in left " "operand of comma operator", type); break; case ICV_STATEMENT: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in statement", type); break; case ICV_THIRD_IN_FOR: warning_at (loc, 0, "indirection will not access object of " "non-trivially-copyable type %qT in for " "increment expression", type); break; default: gcc_unreachable (); } } if (is_reference || !is_volatile || !is_complete || TREE_ADDRESSABLE (type)) { /* Emit a warning (if enabled) when the "effect-less" INDIRECT_REF operation is stripped off. Note that we don't warn about - an expression with TREE_NO_WARNING set. (For an example of such expressions, see build_over_call in call.cc.) - automatic dereferencing of references, since the user cannot control it. (See also warn_if_unused_value() in c-common.cc.) */ if (warn_unused_value && implicit != ICV_CAST && !warning_suppressed_p (expr, OPT_Wunused_value) && !is_reference) warning_at (loc, OPT_Wunused_value, "value computed is not used"); expr = TREE_OPERAND (expr, 0); if (TREE_CODE (expr) == CALL_EXPR) maybe_warn_nodiscard (expr, implicit); } break; } case VAR_DECL: { /* External variables might be incomplete. */ tree type = TREE_TYPE (expr); int is_complete = COMPLETE_TYPE_P (type); if (TYPE_VOLATILE (type) && !is_complete) switch (implicit) { case ICV_CAST: warning_at (loc, 0, "conversion to void will not access " "object %qE of incomplete type %qT", expr, type); break; case ICV_SECOND_OF_COND: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in second operand of " "conditional expression", expr, type); break; case ICV_THIRD_OF_COND: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in third operand of " "conditional expression", expr, type); break; case ICV_RIGHT_OF_COMMA: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in right operand of comma operator", expr, type); break; case ICV_LEFT_OF_COMMA: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in left operand of comma operator", expr, type); break; case ICV_STATEMENT: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in statement", expr, type); break; case ICV_THIRD_IN_FOR: warning_at (loc, 0, "variable %qE of incomplete type %qT will " "not be accessed in for increment expression", expr, type); break; default: gcc_unreachable (); } break; } default:; } if (!TREE_SIDE_EFFECTS (expr)) expr = void_node; return expr; } void maybe_warn_nodiscard (tree expr, impl_conv_void implicit) { tree call = expr; if (TREE_CODE (expr) == TARGET_EXPR) call = TARGET_EXPR_INITIAL (expr); location_t loc = expr_loc_or_input_loc (call); tree callee = CALL_EXPR_FN (call); if (!callee) return; tree type = TREE_TYPE (callee); if (INDIRECT_TYPE_P (type)) type = TREE_TYPE (type); tree rettype = TREE_TYPE (type); tree fn = get_fndecl_from_callee (callee); tree attr; if (implicit != ICV_CAST && fn && (attr = lookup_attribute ("nodiscard", DECL_ATTRIBUTES (fn)))) { escaped_string msg; tree args = TREE_VALUE (attr); if (args) msg.escape (TREE_STRING_POINTER (TREE_VALUE (args))); const char *format = (msg ? G_ ("ignoring return value of %qD, that must be used: %<%s%>") : G_ ("ignoring return value of %qD, that must be used")); const char *raw_msg = msg ? (const char *) msg : ""; auto_diagnostic_group d; if (warning_at (loc, OPT_Wunused_result, format, fn, raw_msg)) inform (DECL_SOURCE_LOCATION (fn), "declared here"); } else if (implicit != ICV_CAST && (attr = lookup_attribute ("nodiscard", TYPE_ATTRIBUTES (rettype)))) { escaped_string msg; tree args = TREE_VALUE (attr); if (args) msg.escape (TREE_STRING_POINTER (TREE_VALUE (args))); const char *format = (msg ? G_ ( "ignoring returned value of type %qT, that must be used: %<%s%>") : G_ ("ignoring returned value of type %qT, that must be used")); const char *raw_msg = msg ? (const char *) msg : ""; auto_diagnostic_group d; if (warning_at (loc, OPT_Wunused_result, format, rettype, raw_msg)) { if (fn) inform (DECL_SOURCE_LOCATION (fn), "in call to %qD, declared here", fn); inform (DECL_SOURCE_LOCATION (TYPE_NAME (rettype)), "%qT declared here", rettype); } } } location_t expr_loc_or_loc (const_tree t, location_t or_loc) { location_t loc = EXPR_LOCATION (t); if (loc == UNKNOWN_LOCATION) loc = or_loc; return loc; } location_t expr_loc_or_input_loc (const_tree t) { return expr_loc_or_loc (t, input_location); } // FN is the callee of a CALL_EXPR or AGGR_INIT_EXPR; return the FUNCTION_DECL // if we can. tree get_fndecl_from_callee (tree fn) { if (fn == NULL_TREE) return fn; if (TREE_CODE (fn) == FUNCTION_DECL) return fn; tree type = TREE_TYPE (fn); if (type == NULL_TREE || !INDIRECT_TYPE_P (type)) return NULL_TREE; STRIP_NOPS (fn); if (TREE_CODE (fn) == ADDR_EXPR || TREE_CODE (fn) == FDESC_EXPR) fn = TREE_OPERAND (fn, 0); if (TREE_CODE (fn) == FUNCTION_DECL) return fn; return NULL_TREE; } tree pointer_offset_expression (tree base_tree, tree index_tree, location_t location) { tree element_type_tree = TREE_TYPE (TREE_TYPE (base_tree)); if (base_tree == error_mark_node || TREE_TYPE (base_tree) == error_mark_node || index_tree == error_mark_node || element_type_tree == error_mark_node) return error_mark_node; tree element_size = TYPE_SIZE_UNIT (element_type_tree); index_tree = fold_convert_loc (location, sizetype, index_tree); tree offset = fold_build2_loc (location, MULT_EXPR, sizetype, index_tree, element_size); return fold_build2_loc (location, POINTER_PLUS_EXPR, TREE_TYPE (base_tree), base_tree, offset); } // forked from gcc/cp/tree.cc cp_walk_subtrees /* Apply FUNC to all language-specific sub-trees of TP in a pre-order traversal. Called from walk_tree. */ tree rs_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func, void *data, hash_set *pset) { enum tree_code code = TREE_CODE (*tp); tree result; #define WALK_SUBTREE(NODE) \ do \ { \ result = rs_walk_tree (&(NODE), func, data, pset); \ if (result) \ goto out; \ } \ while (0) if (TYPE_P (*tp)) { /* If *WALK_SUBTREES_P is 1, we're interested in the syntactic form of the argument, so don't look through typedefs, but do walk into template arguments for alias templates (and non-typedefed classes). If *WALK_SUBTREES_P > 1, we're interested in type identity or equivalence, so look through typedefs, ignoring template arguments for alias templates, and walk into template args of classes. See find_abi_tags_r for an example of setting *WALK_SUBTREES_P to 2 when that's the behavior the walk_tree_fn wants. */ if (*walk_subtrees_p == 1 && typedef_variant_p (*tp)) { *walk_subtrees_p = 0; return NULL_TREE; } } /* Not one of the easy cases. We must explicitly go through the children. */ result = NULL_TREE; switch (code) { case TREE_LIST: WALK_SUBTREE (TREE_PURPOSE (*tp)); break; case RECORD_TYPE: if (TYPE_PTRMEMFUNC_P (*tp)) WALK_SUBTREE (TYPE_PTRMEMFUNC_FN_TYPE_RAW (*tp)); break; case CONSTRUCTOR: if (COMPOUND_LITERAL_P (*tp)) WALK_SUBTREE (TREE_TYPE (*tp)); break; case DECL_EXPR: /* User variables should be mentioned in BIND_EXPR_VARS and their initializers and sizes walked when walking the containing BIND_EXPR. Compiler temporaries are handled here. And also normal variables in templates, since do_poplevel doesn't build a BIND_EXPR then. */ if (VAR_P (TREE_OPERAND (*tp, 0)) && (DECL_ARTIFICIAL (TREE_OPERAND (*tp, 0)) && !TREE_STATIC (TREE_OPERAND (*tp, 0)))) { tree decl = TREE_OPERAND (*tp, 0); WALK_SUBTREE (DECL_INITIAL (decl)); WALK_SUBTREE (DECL_SIZE (decl)); WALK_SUBTREE (DECL_SIZE_UNIT (decl)); } break; default: return NULL_TREE; } /* We didn't find what we were looking for. */ out: return result; #undef WALK_SUBTREE } // forked from gcc/cp/tree.cc cp_expr_location /* Like EXPR_LOCATION, but also handle some tcc_exceptional that have locations. */ location_t rs_expr_location (const_tree t_) { tree t = CONST_CAST_TREE (t_); if (t == NULL_TREE) return UNKNOWN_LOCATION; return EXPR_LOCATION (t); } // forked from gcc/cp/class.cc is_really_empty_class /* Returns true if TYPE contains no actual data, just various possible combinations of empty classes. If IGNORE_VPTR is true, a vptr doesn't prevent the class from being considered empty. Typically we want to ignore the vptr on assignment, and not on initialization. */ bool is_really_empty_class (tree type, bool ignore_vptr) { if (CLASS_TYPE_P (type)) { tree field; tree binfo; tree base_binfo; int i; /* CLASSTYPE_EMPTY_P isn't set properly until the class is actually laid out, but we'd like to be able to check this before then. */ if (COMPLETE_TYPE_P (type) && is_empty_class (type)) return true; if (!ignore_vptr && TYPE_CONTAINS_VPTR_P (type)) return false; for (binfo = TYPE_BINFO (type), i = 0; BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i) if (!is_really_empty_class (BINFO_TYPE (base_binfo), ignore_vptr)) return false; for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) if (TREE_CODE (field) == FIELD_DECL && !DECL_ARTIFICIAL (field) /* An unnamed bit-field is not a data member. */ && !DECL_UNNAMED_BIT_FIELD (field) && !is_really_empty_class (TREE_TYPE (field), ignore_vptr)) return false; return true; } else if (TREE_CODE (type) == ARRAY_TYPE) return (integer_zerop (array_type_nelts_top (type)) || is_really_empty_class (TREE_TYPE (type), ignore_vptr)); return false; } // forked from gcc/cp/class.cc is_empty_class /* Returns 1 if TYPE contains only padding bytes. */ int is_empty_class (tree type) { if (type == error_mark_node) return 0; if (!CLASS_TYPE_P (type)) return 0; return CLASSTYPE_EMPTY_P (type); } // forked from gcc/cp/tree.cc array_type_nelts_top /* Return, as an INTEGER_CST node, the number of elements for TYPE (which is an ARRAY_TYPE). This counts only elements of the top array. */ tree array_type_nelts_top (tree type) { return fold_build2_loc (input_location, PLUS_EXPR, sizetype, array_type_nelts (type), size_one_node); } // forked from gcc/cp/tree.cc builtin_valid_in_constant_expr_p /* Test whether DECL is a builtin that may appear in a constant-expression. */ bool builtin_valid_in_constant_expr_p (const_tree decl) { STRIP_ANY_LOCATION_WRAPPER (decl); if (TREE_CODE (decl) != FUNCTION_DECL) /* Not a function. */ return false; if (DECL_BUILT_IN_CLASS (decl) != BUILT_IN_NORMAL) { if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND)) switch (DECL_FE_FUNCTION_CODE (decl)) { case RS_BUILT_IN_IS_CONSTANT_EVALUATED: case RS_BUILT_IN_SOURCE_LOCATION: case RS_BUILT_IN_IS_CORRESPONDING_MEMBER: case RS_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS: return true; default: break; } /* Not a built-in. */ return false; } switch (DECL_FUNCTION_CODE (decl)) { /* These always have constant results like the corresponding macros/symbol. */ case BUILT_IN_FILE: case BUILT_IN_FUNCTION: case BUILT_IN_LINE: /* The following built-ins are valid in constant expressions when their arguments are. */ case BUILT_IN_ADD_OVERFLOW_P: case BUILT_IN_SUB_OVERFLOW_P: case BUILT_IN_MUL_OVERFLOW_P: /* These have constant results even if their operands are non-constant. */ case BUILT_IN_CONSTANT_P: case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: return true; default: return false; } } // forked from gcc/cp/decl2.cc decl_maybe_constant_var_p /* Returns true if DECL could be a symbolic constant variable, depending on its initializer. */ bool decl_maybe_constant_var_p (tree decl) { tree type = TREE_TYPE (decl); if (!VAR_P (decl)) return false; if (DECL_DECLARED_CONSTEXPR_P (decl)) return true; if (DECL_HAS_VALUE_EXPR_P (decl)) /* A proxy isn't constant. */ return false; if (TYPE_REF_P (type)) /* References can be constant. */; else if (RS_TYPE_CONST_NON_VOLATILE_P (type) && INTEGRAL_OR_ENUMERATION_TYPE_P (type)) /* And const integers. */; else return false; if (DECL_INITIAL (decl) && !DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl)) /* We know the initializer, and it isn't constant. */ return false; else return true; } // forked from gcc/cp/typeck.cc cp_type_quals /* Returns the type qualifiers for this type, including the qualifiers on the elements for an array type. */ int rs_type_quals (const_tree type) { int quals; /* This CONST_CAST is okay because strip_array_types returns its argument unmodified and we assign it to a const_tree. */ type = strip_array_types (CONST_CAST_TREE (type)); if (type == error_mark_node /* Quals on a FUNCTION_TYPE are memfn quals. */ || TREE_CODE (type) == FUNCTION_TYPE) return TYPE_UNQUALIFIED; quals = TYPE_QUALS (type); /* METHOD and REFERENCE_TYPEs should never have quals. */ gcc_assert ( (TREE_CODE (type) != METHOD_TYPE && !TYPE_REF_P (type)) || ((quals & (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE)) == TYPE_UNQUALIFIED)); return quals; } } // namespace Rust