Add inclusive range support for Rust

This is version 2 of the patch to add inclusive range support for
Rust.  I believe it addresses all review comments.

Rust recently stabilized the inclusive range feature:

    https://github.com/rust-lang/rust/issues/28237

An inclusive range is an expression like "..= EXPR" or "EXPR ..=
EXPR".  It is like an ordinary range, except the upper bound is
inclusive, not exclusive.

This patch adds support for this feature to gdb.

Regression tested on x86-64 Fedora 27.

2018-04-27  Tom Tromey  <tom@tromey.com>

	PR rust/22545:
	* rust-lang.c (rust_inclusive_range_type_p): New function.
	(rust_range): Handle inclusive ranges.
	(rust_compute_range): Likewise.
	* rust-exp.y (struct rust_op) <inclusive>: New field.
	(DOTDOTEQ): New constant.
	(range_expr): Add "..=" productions.
	(operator_tokens): Add "..=" token.
	(ast_range): Add "inclusive" parameter.
	(convert_ast_to_expression) <case OP_RANGE>: Handle inclusive
	ranges.
	* parse.c (operator_length_standard) <case OP_RANGE>: Handle new
	bounds values.
	* expression.h (enum range_type) <NONE_BOUND_DEFAULT_EXCLUSIVE,
	LOW_BOUND_DEFAULT_EXCLUSIVE>: New constants.
	Update comments.
	* expprint.c (print_subexp_standard): Handle new bounds values.
	(dump_subexp_body_standard): Likewise.

2018-04-27  Tom Tromey  <tom@tromey.com>

	PR rust/22545:
	* gdb.rust/simple.exp: Add inclusive range tests.
This commit is contained in:
Tom Tromey 2018-03-29 14:14:07 -06:00
parent 632e107b32
commit 6873858b7e
8 changed files with 117 additions and 23 deletions

View File

@ -1,3 +1,24 @@
2018-04-27 Tom Tromey <tom@tromey.com>
PR rust/22545:
* rust-lang.c (rust_inclusive_range_type_p): New function.
(rust_range): Handle inclusive ranges.
(rust_compute_range): Likewise.
* rust-exp.y (struct rust_op) <inclusive>: New field.
(DOTDOTEQ): New constant.
(range_expr): Add "..=" productions.
(operator_tokens): Add "..=" token.
(ast_range): Add "inclusive" parameter.
(convert_ast_to_expression) <case OP_RANGE>: Handle inclusive
ranges.
* parse.c (operator_length_standard) <case OP_RANGE>: Handle new
bounds values.
* expression.h (enum range_type) <NONE_BOUND_DEFAULT_EXCLUSIVE,
LOW_BOUND_DEFAULT_EXCLUSIVE>: New constants.
Update comments.
* expprint.c (print_subexp_standard): Handle new bounds values.
(dump_subexp_body_standard): Likewise.
2018-04-27 Tom Tromey <tom@tromey.com> 2018-04-27 Tom Tromey <tom@tromey.com>
* configure: Rebuild. * configure: Rebuild.

View File

@ -578,9 +578,13 @@ print_subexp_standard (struct expression *exp, int *pos,
longest_to_int (exp->elts[pc + 1].longconst); longest_to_int (exp->elts[pc + 1].longconst);
*pos += 2; *pos += 2;
if (range_type == NONE_BOUND_DEFAULT_EXCLUSIVE
|| range_type == LOW_BOUND_DEFAULT_EXCLUSIVE)
fputs_filtered ("EXCLUSIVE_", stream);
fputs_filtered ("RANGE(", stream); fputs_filtered ("RANGE(", stream);
if (range_type == HIGH_BOUND_DEFAULT if (range_type == HIGH_BOUND_DEFAULT
|| range_type == NONE_BOUND_DEFAULT) || range_type == NONE_BOUND_DEFAULT
|| range_type == NONE_BOUND_DEFAULT_EXCLUSIVE)
print_subexp (exp, pos, stream, PREC_ABOVE_COMMA); print_subexp (exp, pos, stream, PREC_ABOVE_COMMA);
fputs_filtered ("..", stream); fputs_filtered ("..", stream);
if (range_type == LOW_BOUND_DEFAULT if (range_type == LOW_BOUND_DEFAULT
@ -1099,12 +1103,18 @@ dump_subexp_body_standard (struct expression *exp,
case LOW_BOUND_DEFAULT: case LOW_BOUND_DEFAULT:
fputs_filtered ("Range '..EXP'", stream); fputs_filtered ("Range '..EXP'", stream);
break; break;
case LOW_BOUND_DEFAULT_EXCLUSIVE:
fputs_filtered ("ExclusiveRange '..EXP'", stream);
break;
case HIGH_BOUND_DEFAULT: case HIGH_BOUND_DEFAULT:
fputs_filtered ("Range 'EXP..'", stream); fputs_filtered ("Range 'EXP..'", stream);
break; break;
case NONE_BOUND_DEFAULT: case NONE_BOUND_DEFAULT:
fputs_filtered ("Range 'EXP..EXP'", stream); fputs_filtered ("Range 'EXP..EXP'", stream);
break; break;
case NONE_BOUND_DEFAULT_EXCLUSIVE:
fputs_filtered ("ExclusiveRange 'EXP..EXP'", stream);
break;
default: default:
fputs_filtered ("Invalid Range!", stream); fputs_filtered ("Invalid Range!", stream);
break; break;

View File

@ -150,15 +150,26 @@ extern void dump_prefix_expression (struct expression *, struct ui_file *);
/* In an OP_RANGE expression, either bound could be empty, indicating /* In an OP_RANGE expression, either bound could be empty, indicating
that its value is by default that of the corresponding bound of the that its value is by default that of the corresponding bound of the
array or string. So we have four sorts of subrange. This array or string. Also, the upper end of the range can be exclusive
enumeration type is to identify this. */ or inclusive. So we have six sorts of subrange. This enumeration
type is to identify this. */
enum range_type enum range_type
{ {
BOTH_BOUND_DEFAULT, /* "(:)" */ /* Neither the low nor the high bound was given -- so this refers to
LOW_BOUND_DEFAULT, /* "(:high)" */ the entire available range. */
HIGH_BOUND_DEFAULT, /* "(low:)" */ BOTH_BOUND_DEFAULT,
NONE_BOUND_DEFAULT /* "(low:high)" */ /* The low bound was not given and the high bound is inclusive. */
}; LOW_BOUND_DEFAULT,
/* The high bound was not given and the low bound in inclusive. */
HIGH_BOUND_DEFAULT,
/* Both bounds were given and both are inclusive. */
NONE_BOUND_DEFAULT,
/* The low bound was not given and the high bound is exclusive. */
NONE_BOUND_DEFAULT_EXCLUSIVE,
/* Both bounds were given. The low bound is inclusive and the high
bound is exclusive. */
LOW_BOUND_DEFAULT_EXCLUSIVE,
};
#endif /* !defined (EXPRESSION_H) */ #endif /* !defined (EXPRESSION_H) */

View File

@ -995,6 +995,7 @@ operator_length_standard (const struct expression *expr, int endpos,
switch (range_type) switch (range_type)
{ {
case LOW_BOUND_DEFAULT: case LOW_BOUND_DEFAULT:
case LOW_BOUND_DEFAULT_EXCLUSIVE:
case HIGH_BOUND_DEFAULT: case HIGH_BOUND_DEFAULT:
args = 1; args = 1;
break; break;
@ -1002,6 +1003,7 @@ operator_length_standard (const struct expression *expr, int endpos,
args = 0; args = 0;
break; break;
case NONE_BOUND_DEFAULT: case NONE_BOUND_DEFAULT:
case NONE_BOUND_DEFAULT_EXCLUSIVE:
args = 2; args = 2;
break; break;
} }

View File

@ -111,7 +111,8 @@ static const struct rust_op *ast_string (struct stoken str);
static const struct rust_op *ast_struct (const struct rust_op *name, static const struct rust_op *ast_struct (const struct rust_op *name,
rust_set_vector *fields); rust_set_vector *fields);
static const struct rust_op *ast_range (const struct rust_op *lhs, static const struct rust_op *ast_range (const struct rust_op *lhs,
const struct rust_op *rhs); const struct rust_op *rhs,
bool inclusive);
static const struct rust_op *ast_array_type (const struct rust_op *lhs, static const struct rust_op *ast_array_type (const struct rust_op *lhs,
struct typed_val_int val); struct typed_val_int val);
static const struct rust_op *ast_slice_type (const struct rust_op *type); static const struct rust_op *ast_slice_type (const struct rust_op *type);
@ -300,6 +301,9 @@ struct rust_op
name occurred at the end of the expression and is eligible for name occurred at the end of the expression and is eligible for
completion. */ completion. */
unsigned int completing : 1; unsigned int completing : 1;
/* For OP_RANGE, indicates whether the range is inclusive or
exclusive. */
unsigned int inclusive : 1;
/* Operands of expression. Which one is used and how depends on the /* Operands of expression. Which one is used and how depends on the
particular opcode. */ particular opcode. */
RUSTSTYPE left; RUSTSTYPE left;
@ -333,6 +337,7 @@ struct rust_op
/* Operator tokens. */ /* Operator tokens. */
%token <voidval> DOTDOT %token <voidval> DOTDOT
%token <voidval> DOTDOTEQ
%token <voidval> OROR %token <voidval> OROR
%token <voidval> ANDAND %token <voidval> ANDAND
%token <voidval> EQEQ %token <voidval> EQEQ
@ -382,7 +387,7 @@ struct rust_op
%type <one_field_init> struct_expr_tail %type <one_field_init> struct_expr_tail
/* Precedence. */ /* Precedence. */
%nonassoc DOTDOT %nonassoc DOTDOT DOTDOTEQ
%right '=' COMPOUND_ASSIGN %right '=' COMPOUND_ASSIGN
%left OROR %left OROR
%left ANDAND %left ANDAND
@ -535,13 +540,17 @@ array_expr:
range_expr: range_expr:
expr DOTDOT expr DOTDOT
{ $$ = ast_range ($1, NULL); } { $$ = ast_range ($1, NULL, false); }
| expr DOTDOT expr | expr DOTDOT expr
{ $$ = ast_range ($1, $3); } { $$ = ast_range ($1, $3, false); }
| expr DOTDOTEQ expr
{ $$ = ast_range ($1, $3, true); }
| DOTDOT expr | DOTDOT expr
{ $$ = ast_range (NULL, $2); } { $$ = ast_range (NULL, $2, false); }
| DOTDOTEQ expr
{ $$ = ast_range (NULL, $2, true); }
| DOTDOT | DOTDOT
{ $$ = ast_range (NULL, NULL); } { $$ = ast_range (NULL, NULL, false); }
; ;
literal: literal:
@ -956,6 +965,7 @@ static const struct token_info operator_tokens[] =
{ "&=", COMPOUND_ASSIGN, BINOP_BITWISE_AND }, { "&=", COMPOUND_ASSIGN, BINOP_BITWISE_AND },
{ "|=", COMPOUND_ASSIGN, BINOP_BITWISE_IOR }, { "|=", COMPOUND_ASSIGN, BINOP_BITWISE_IOR },
{ "^=", COMPOUND_ASSIGN, BINOP_BITWISE_XOR }, { "^=", COMPOUND_ASSIGN, BINOP_BITWISE_XOR },
{ "..=", DOTDOTEQ, OP_NULL },
{ "::", COLONCOLON, OP_NULL }, { "::", COLONCOLON, OP_NULL },
{ "..", DOTDOT, OP_NULL }, { "..", DOTDOT, OP_NULL },
@ -1841,11 +1851,13 @@ ast_structop_anonymous (const struct rust_op *left,
/* Make a range operation. */ /* Make a range operation. */
static const struct rust_op * static const struct rust_op *
ast_range (const struct rust_op *lhs, const struct rust_op *rhs) ast_range (const struct rust_op *lhs, const struct rust_op *rhs,
bool inclusive)
{ {
struct rust_op *result = OBSTACK_ZALLOC (work_obstack, struct rust_op); struct rust_op *result = OBSTACK_ZALLOC (work_obstack, struct rust_op);
result->opcode = OP_RANGE; result->opcode = OP_RANGE;
result->inclusive = inclusive;
result->left.op = lhs; result->left.op = lhs;
result->right.op = rhs; result->right.op = rhs;
@ -2473,13 +2485,22 @@ convert_ast_to_expression (struct parser_state *state,
{ {
convert_ast_to_expression (state, operation->right.op, top); convert_ast_to_expression (state, operation->right.op, top);
if (kind == BOTH_BOUND_DEFAULT) if (kind == BOTH_BOUND_DEFAULT)
kind = LOW_BOUND_DEFAULT; kind = (operation->inclusive
? LOW_BOUND_DEFAULT : LOW_BOUND_DEFAULT_EXCLUSIVE);
else else
{ {
gdb_assert (kind == HIGH_BOUND_DEFAULT); gdb_assert (kind == HIGH_BOUND_DEFAULT);
kind = NONE_BOUND_DEFAULT; kind = (operation->inclusive
? NONE_BOUND_DEFAULT : NONE_BOUND_DEFAULT_EXCLUSIVE);
} }
} }
else
{
/* Nothing should make an inclusive range without an upper
bound. */
gdb_assert (!operation->inclusive);
}
write_exp_elt_opcode (state, OP_RANGE); write_exp_elt_opcode (state, OP_RANGE);
write_exp_elt_longcst (state, kind); write_exp_elt_longcst (state, kind);
write_exp_elt_opcode (state, OP_RANGE); write_exp_elt_opcode (state, OP_RANGE);

View File

@ -180,6 +180,17 @@ rust_range_type_p (struct type *type)
return strcmp (TYPE_FIELD_NAME (type, i), "end") == 0; return strcmp (TYPE_FIELD_NAME (type, i), "end") == 0;
} }
/* Return true if TYPE is an inclusive range type, otherwise false.
This is only valid for types which are already known to be range
types. */
static bool
rust_inclusive_range_type_p (struct type *type)
{
return (strstr (TYPE_TAG_NAME (type), "::RangeInclusive") != NULL
|| strstr (TYPE_TAG_NAME (type), "::RangeToInclusive") != NULL);
}
/* Return true if TYPE seems to be the type "u8", otherwise false. */ /* Return true if TYPE seems to be the type "u8", otherwise false. */
static bool static bool
@ -1136,10 +1147,13 @@ rust_range (struct expression *exp, int *pos, enum noside noside)
kind = (enum range_type) longest_to_int (exp->elts[*pos + 1].longconst); kind = (enum range_type) longest_to_int (exp->elts[*pos + 1].longconst);
*pos += 3; *pos += 3;
if (kind == HIGH_BOUND_DEFAULT || kind == NONE_BOUND_DEFAULT) if (kind == HIGH_BOUND_DEFAULT || kind == NONE_BOUND_DEFAULT
|| kind == NONE_BOUND_DEFAULT_EXCLUSIVE)
low = evaluate_subexp (NULL_TYPE, exp, pos, noside); low = evaluate_subexp (NULL_TYPE, exp, pos, noside);
if (kind == LOW_BOUND_DEFAULT || kind == NONE_BOUND_DEFAULT) if (kind == LOW_BOUND_DEFAULT || kind == LOW_BOUND_DEFAULT_EXCLUSIVE
|| kind == NONE_BOUND_DEFAULT || kind == NONE_BOUND_DEFAULT_EXCLUSIVE)
high = evaluate_subexp (NULL_TYPE, exp, pos, noside); high = evaluate_subexp (NULL_TYPE, exp, pos, noside);
bool inclusive = (kind == NONE_BOUND_DEFAULT || kind == LOW_BOUND_DEFAULT);
if (noside == EVAL_SKIP) if (noside == EVAL_SKIP)
return value_from_longest (builtin_type (exp->gdbarch)->builtin_int, 1); return value_from_longest (builtin_type (exp->gdbarch)->builtin_int, 1);
@ -1154,7 +1168,8 @@ rust_range (struct expression *exp, int *pos, enum noside noside)
else else
{ {
index_type = value_type (high); index_type = value_type (high);
name = "std::ops::RangeTo"; name = (inclusive
? "std::ops::RangeToInclusive" : "std::ops::RangeTo");
} }
} }
else else
@ -1169,7 +1184,7 @@ rust_range (struct expression *exp, int *pos, enum noside noside)
if (!types_equal (value_type (low), value_type (high))) if (!types_equal (value_type (low), value_type (high)))
error (_("Range expression with different types")); error (_("Range expression with different types"));
index_type = value_type (low); index_type = value_type (low);
name = "std::ops::Range"; name = inclusive ? "std::ops::RangeInclusive" : "std::ops::Range";
} }
} }
@ -1245,6 +1260,9 @@ rust_compute_range (struct type *type, struct value *range,
*kind = (*kind == BOTH_BOUND_DEFAULT *kind = (*kind == BOTH_BOUND_DEFAULT
? LOW_BOUND_DEFAULT : NONE_BOUND_DEFAULT); ? LOW_BOUND_DEFAULT : NONE_BOUND_DEFAULT);
*high = value_as_long (value_field (range, i)); *high = value_as_long (value_field (range, i));
if (rust_inclusive_range_type_p (type))
++*high;
} }
} }

View File

@ -1,3 +1,8 @@
2018-04-27 Tom Tromey <tom@tromey.com>
PR rust/22545:
* gdb.rust/simple.exp: Add inclusive range tests.
2018-04-26 Pedro Alves <palves@redhat.com> 2018-04-26 Pedro Alves <palves@redhat.com>
* gdb.base/gnu-ifunc.exp (set-break): Test that GDB resolves * gdb.base/gnu-ifunc.exp (set-break): Test that GDB resolves

View File

@ -219,7 +219,9 @@ gdb_test "print r###\"###hello\"##" "Unexpected EOF in string"
gdb_test "print r###\"hello###" "Unexpected EOF in string" gdb_test "print r###\"hello###" "Unexpected EOF in string"
gdb_test "print 0..5" " = .*::ops::Range.* \\{start: 0, end: 5\\}" gdb_test "print 0..5" " = .*::ops::Range.* \\{start: 0, end: 5\\}"
gdb_test "print 0..=5" " = .*::ops::RangeInclusive.* \\{start: 0, end: 5\\}"
gdb_test "print ..5" " = .*::ops::RangeTo.* \\{end: 5\\}" gdb_test "print ..5" " = .*::ops::RangeTo.* \\{end: 5\\}"
gdb_test "print ..=5" " = .*::ops::RangeToInclusive.* \\{end: 5\\}"
gdb_test "print 5.." " = .*::ops::RangeFrom.* \\{start: 5\\}" gdb_test "print 5.." " = .*::ops::RangeFrom.* \\{start: 5\\}"
gdb_test "print .." " = .*::ops::RangeFull" gdb_test "print .." " = .*::ops::RangeFull"
@ -244,7 +246,9 @@ proc test_one_slice {svar length base range} {
} }
test_one_slice slice 1 w 2..3 test_one_slice slice 1 w 2..3
test_one_slice slice 1 w 2..=2
test_one_slice slice2 1 slice 0..1 test_one_slice slice2 1 slice 0..1
test_one_slice slice2 1 slice 0..=0
test_one_slice all1 4 w .. test_one_slice all1 4 w ..
test_one_slice all2 1 slice .. test_one_slice all2 1 slice ..
@ -253,7 +257,9 @@ test_one_slice from1 3 w 1..
test_one_slice from2 0 slice 1.. test_one_slice from2 0 slice 1..
test_one_slice to1 3 w ..3 test_one_slice to1 3 w ..3
test_one_slice to1 3 w ..=2
test_one_slice to2 1 slice ..1 test_one_slice to2 1 slice ..1
test_one_slice to2 1 slice ..=0
gdb_test "print w\[2..3\]" "Can't take slice of array without '&'" gdb_test "print w\[2..3\]" "Can't take slice of array without '&'"