Skip to content

Commit 94b03af

Browse files
authored
Rollup merge of rust-lang#119532 - GKFX:offset-of-parse-expr, r=est31
Make offset_of field parsing use metavariable which handles any spacing As discussed at and around comments rust-lang#106655 (comment) and rust-lang#106655 (comment), the current arguments to offset_of do not accept all the whitespace combinations: `0. 1.1.1` and `0.1.1. 1` are currently treated specially in `tests/ui/offset-of/offset-of-tuple-nested.rs`. They also do not allow [forwarding individual fields as in](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=444cdf0ec02b99e8fd5fd8d8ecb312ca) ```rust macro_rules! off { ($a:expr) => { offset_of!(m::S, 0. $a) } } ``` This PR replaces the macro arguments with `($Container:ty, $($fields:expr)+ $(,)?)` which does allow any arrangement of whitespace that I could come up with and the forwarding of fields example above. This also allows for array indexing in the future, which I think is the last future extension to the syntax suggested in the offset_of RFC. Tracking issue for offset_of: rust-lang#106655 `@rustbot` label F-offset_of `@est31`
2 parents 6129278 + f0c0a49 commit 94b03af

13 files changed

+293
-263
lines changed

compiler/rustc_parse/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ parse_ambiguous_range_pattern = the range pattern here has ambiguous interpretat
1010
parse_array_brackets_instead_of_braces = this is a block expression, not an array
1111
.suggestion = to make an array, use square brackets instead of curly braces
1212
13+
parse_array_index_offset_of = array indexing not supported in offset_of
14+
1315
parse_assignment_else_not_allowed = <assignment> ... else {"{"} ... {"}"} is not allowed
1416
1517
parse_assoc_lifetime = associated lifetimes are not supported
@@ -405,6 +407,8 @@ parse_invalid_logical_operator = `{$incorrect}` is not a logical operator
405407
406408
parse_invalid_meta_item = expected unsuffixed literal or identifier, found `{$token}`
407409
410+
parse_invalid_offset_of = offset_of expects dot-separated field and variant names
411+
408412
parse_invalid_unicode_escape = invalid unicode character escape
409413
.label = invalid escape
410414
.help = unicode escape must {$surrogate ->

compiler/rustc_parse/src/errors.rs

+8
Original file line numberDiff line numberDiff line change
@@ -2887,3 +2887,11 @@ pub(crate) struct TransposeDynOrImplSugg<'a> {
28872887
pub insertion_span: Span,
28882888
pub kw: &'a str,
28892889
}
2890+
2891+
#[derive(Diagnostic)]
2892+
#[diag(parse_array_index_offset_of)]
2893+
pub(crate) struct ArrayIndexInOffsetOf(#[primary_span] pub Span);
2894+
2895+
#[derive(Diagnostic)]
2896+
#[diag(parse_invalid_offset_of)]
2897+
pub(crate) struct InvalidOffsetOf(#[primary_span] pub Span);

compiler/rustc_parse/src/parser/expr.rs

+121-43
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ impl<'a> Parser<'a> {
10231023
// we should break everything including floats into more basic proc-macro style
10241024
// tokens in the lexer (probably preferable).
10251025
// See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`.
1026-
fn break_up_float(&mut self, float: Symbol) -> DestructuredFloat {
1026+
fn break_up_float(&self, float: Symbol, span: Span) -> DestructuredFloat {
10271027
#[derive(Debug)]
10281028
enum FloatComponent {
10291029
IdentLike(String),
@@ -1053,7 +1053,6 @@ impl<'a> Parser<'a> {
10531053
// With proc macros the span can refer to anything, the source may be too short,
10541054
// or too long, or non-ASCII. It only makes sense to break our span into components
10551055
// if its underlying text is identical to our float literal.
1056-
let span = self.token.span;
10571056
let can_take_span_apart =
10581057
|| self.span_to_snippet(span).as_deref() == Ok(float_str).as_deref();
10591058

@@ -1115,7 +1114,7 @@ impl<'a> Parser<'a> {
11151114
float: Symbol,
11161115
suffix: Option<Symbol>,
11171116
) -> P<Expr> {
1118-
match self.break_up_float(float) {
1117+
match self.break_up_float(float, self.token.span) {
11191118
// 1e2
11201119
DestructuredFloat::Single(sym, _sp) => {
11211120
self.parse_expr_tuple_field_access(lo, base, sym, suffix, None)
@@ -1143,40 +1142,105 @@ impl<'a> Parser<'a> {
11431142
}
11441143
}
11451144

1146-
fn parse_field_name_maybe_tuple(&mut self) -> PResult<'a, ThinVec<Ident>> {
1147-
let token::Literal(token::Lit { kind: token::Float, symbol, suffix }) = self.token.kind
1148-
else {
1149-
return Ok(thin_vec![self.parse_field_name()?]);
1150-
};
1151-
Ok(match self.break_up_float(symbol) {
1152-
// 1e2
1153-
DestructuredFloat::Single(sym, sp) => {
1154-
self.bump();
1155-
thin_vec![Ident::new(sym, sp)]
1156-
}
1157-
// 1.
1158-
DestructuredFloat::TrailingDot(sym, sym_span, dot_span) => {
1159-
assert!(suffix.is_none());
1160-
// Analogous to `Self::break_and_eat`
1161-
self.break_last_token = true;
1162-
// This might work, in cases like `1. 2`, and might not,
1163-
// in cases like `offset_of!(Ty, 1.)`. It depends on what comes
1164-
// after the float-like token, and therefore we have to make
1165-
// the other parts of the parser think that there is a dot literal.
1166-
self.token = Token::new(token::Ident(sym, false), sym_span);
1167-
self.bump_with((Token::new(token::Dot, dot_span), self.token_spacing));
1168-
thin_vec![Ident::new(sym, sym_span)]
1169-
}
1170-
// 1.2 | 1.2e3
1171-
DestructuredFloat::MiddleDot(symbol1, ident1_span, _dot_span, symbol2, ident2_span) => {
1172-
self.bump();
1173-
thin_vec![Ident::new(symbol1, ident1_span), Ident::new(symbol2, ident2_span)]
1145+
/// Parse the field access used in offset_of, matched by `$(e:expr)+`.
1146+
/// Currently returns a list of idents. However, it should be possible in
1147+
/// future to also do array indices, which might be arbitrary expressions.
1148+
fn parse_floating_field_access(&mut self) -> PResult<'a, P<[Ident]>> {
1149+
let mut fields = Vec::new();
1150+
let mut trailing_dot = None;
1151+
1152+
loop {
1153+
// This is expected to use a metavariable $(args:expr)+, but the builtin syntax
1154+
// could be called directly. Calling `parse_expr` allows this function to only
1155+
// consider `Expr`s.
1156+
let expr = self.parse_expr()?;
1157+
let mut current = &expr;
1158+
let start_idx = fields.len();
1159+
loop {
1160+
match current.kind {
1161+
ExprKind::Field(ref left, right) => {
1162+
// Field access is read right-to-left.
1163+
fields.insert(start_idx, right);
1164+
trailing_dot = None;
1165+
current = left;
1166+
}
1167+
// Parse this both to give helpful error messages and to
1168+
// verify it can be done with this parser setup.
1169+
ExprKind::Index(ref left, ref _right, span) => {
1170+
self.dcx().emit_err(errors::ArrayIndexInOffsetOf(span));
1171+
current = left;
1172+
}
1173+
ExprKind::Lit(token::Lit {
1174+
kind: token::Float | token::Integer,
1175+
symbol,
1176+
suffix,
1177+
}) => {
1178+
if let Some(suffix) = suffix {
1179+
self.expect_no_tuple_index_suffix(current.span, suffix);
1180+
}
1181+
match self.break_up_float(symbol, current.span) {
1182+
// 1e2
1183+
DestructuredFloat::Single(sym, sp) => {
1184+
trailing_dot = None;
1185+
fields.insert(start_idx, Ident::new(sym, sp));
1186+
}
1187+
// 1.
1188+
DestructuredFloat::TrailingDot(sym, sym_span, dot_span) => {
1189+
assert!(suffix.is_none());
1190+
trailing_dot = Some(dot_span);
1191+
fields.insert(start_idx, Ident::new(sym, sym_span));
1192+
}
1193+
// 1.2 | 1.2e3
1194+
DestructuredFloat::MiddleDot(
1195+
symbol1,
1196+
span1,
1197+
_dot_span,
1198+
symbol2,
1199+
span2,
1200+
) => {
1201+
trailing_dot = None;
1202+
fields.insert(start_idx, Ident::new(symbol2, span2));
1203+
fields.insert(start_idx, Ident::new(symbol1, span1));
1204+
}
1205+
DestructuredFloat::Error => {
1206+
trailing_dot = None;
1207+
fields.insert(start_idx, Ident::new(symbol, self.prev_token.span));
1208+
}
1209+
}
1210+
break;
1211+
}
1212+
ExprKind::Path(None, Path { ref segments, .. }) => {
1213+
match &segments[..] {
1214+
[PathSegment { ident, args: None, .. }] => {
1215+
trailing_dot = None;
1216+
fields.insert(start_idx, *ident)
1217+
}
1218+
_ => {
1219+
self.dcx().emit_err(errors::InvalidOffsetOf(current.span));
1220+
break;
1221+
}
1222+
}
1223+
break;
1224+
}
1225+
_ => {
1226+
self.dcx().emit_err(errors::InvalidOffsetOf(current.span));
1227+
break;
1228+
}
1229+
}
11741230
}
1175-
DestructuredFloat::Error => {
1176-
self.bump();
1177-
thin_vec![Ident::new(symbol, self.prev_token.span)]
1231+
1232+
if matches!(self.token.kind, token::CloseDelim(..) | token::Comma) {
1233+
break;
1234+
} else if trailing_dot.is_none() {
1235+
// This loop should only repeat if there is a trailing dot.
1236+
self.dcx().emit_err(errors::InvalidOffsetOf(self.token.span));
1237+
break;
11781238
}
1179-
})
1239+
}
1240+
if let Some(dot) = trailing_dot {
1241+
self.dcx().emit_err(errors::InvalidOffsetOf(dot));
1242+
}
1243+
Ok(fields.into_iter().collect())
11801244
}
11811245

11821246
fn parse_expr_tuple_field_access(
@@ -1907,15 +1971,29 @@ impl<'a> Parser<'a> {
19071971
let container = self.parse_ty()?;
19081972
self.expect(&TokenKind::Comma)?;
19091973

1910-
let seq_sep = SeqSep { sep: Some(token::Dot), trailing_sep_allowed: false };
1911-
let (fields, _trailing, _recovered) = self.parse_seq_to_before_end(
1912-
&TokenKind::CloseDelim(Delimiter::Parenthesis),
1913-
seq_sep,
1914-
Parser::parse_field_name_maybe_tuple,
1915-
)?;
1916-
let fields = fields.into_iter().flatten().collect::<Vec<_>>();
1974+
let fields = self.parse_floating_field_access()?;
1975+
let trailing_comma = self.eat_noexpect(&TokenKind::Comma);
1976+
1977+
if let Err(mut e) =
1978+
self.expect_one_of(&[], &[TokenKind::CloseDelim(Delimiter::Parenthesis)])
1979+
{
1980+
if trailing_comma {
1981+
e.note("unexpected third argument to offset_of");
1982+
} else {
1983+
e.note("offset_of expects dot-separated field and variant names");
1984+
}
1985+
e.emit();
1986+
}
1987+
1988+
// Eat tokens until the macro call ends.
1989+
if self.may_recover() {
1990+
while !matches!(self.token.kind, token::CloseDelim(..) | token::Eof) {
1991+
self.bump();
1992+
}
1993+
}
1994+
19171995
let span = lo.to(self.token.span);
1918-
Ok(self.mk_expr(span, ExprKind::OffsetOf(container, fields.into())))
1996+
Ok(self.mk_expr(span, ExprKind::OffsetOf(container, fields)))
19191997
}
19201998

19211999
/// Returns a string literal if the next token is a string literal.

library/core/src/mem/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1395,8 +1395,18 @@ impl<T> SizedTypeProperties for T {}
13951395
///
13961396
/// assert_eq!(mem::offset_of!(Option<&u8>, Some.0), 0);
13971397
/// ```
1398+
#[cfg(not(bootstrap))]
13981399
#[unstable(feature = "offset_of", issue = "106655")]
13991400
#[allow_internal_unstable(builtin_syntax, hint_must_use)]
1401+
pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) {
1402+
// The `{}` is for better error messages
1403+
crate::hint::must_use({builtin # offset_of($Container, $($fields)+)})
1404+
}
1405+
1406+
#[cfg(bootstrap)]
1407+
#[unstable(feature = "offset_of", issue = "106655")]
1408+
#[allow_internal_unstable(builtin_syntax, hint_must_use)]
1409+
#[allow(missing_docs)]
14001410
pub macro offset_of($Container:ty, $($fields:tt).+ $(,)?) {
14011411
// The `{}` is for better error messages
14021412
crate::hint::must_use({builtin # offset_of($Container, $($fields).+)})

tests/ui/offset-of/offset-of-arg-count.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ fn main() {
88
offset_of!(Container, field, too many arguments); //~ ERROR no rules expected the token `too`
99
offset_of!(S, f); // compiles fine
1010
offset_of!(S, f,); // also compiles fine
11-
offset_of!(S, f.); //~ ERROR unexpected end of macro invocation
12-
offset_of!(S, f.,); //~ ERROR expected identifier
13-
offset_of!(S, f..); //~ ERROR no rules expected the token
14-
offset_of!(S, f..,); //~ ERROR no rules expected the token
11+
offset_of!(S, f.); //~ ERROR unexpected token: `)`
12+
offset_of!(S, f.,); //~ ERROR unexpected token: `,`
13+
offset_of!(S, f..); //~ ERROR offset_of expects dot-separated field and variant names
14+
offset_of!(S, f..,); //~ ERROR offset_of expects dot-separated field and variant names
1515
offset_of!(Lt<'static>, bar); // issue #111657
1616
offset_of!(Lt<'_>, bar); // issue #111678
1717
}

tests/ui/offset-of/offset-of-arg-count.stderr

+11-18
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ error: unexpected end of macro invocation
1313
LL | offset_of!(NotEnoughArgumentsWithAComma, );
1414
| ^ missing tokens in macro arguments
1515
|
16-
note: while trying to match meta-variable `$fields:tt`
16+
note: while trying to match meta-variable `$fields:expr`
1717
--> $SRC_DIR/core/src/mem/mod.rs:LL:COL
1818

1919
error: no rules expected the token `too`
@@ -24,36 +24,29 @@ LL | offset_of!(Container, field, too many arguments);
2424
|
2525
= note: while trying to match sequence end
2626

27-
error: unexpected end of macro invocation
27+
error: unexpected token: `)`
2828
--> $DIR/offset-of-arg-count.rs:11:21
2929
|
3030
LL | offset_of!(S, f.);
31-
| ^ missing tokens in macro arguments
32-
|
33-
note: while trying to match meta-variable `$fields:tt`
34-
--> $SRC_DIR/core/src/mem/mod.rs:LL:COL
31+
| ^
3532

36-
error: expected identifier, found `,`
33+
error: unexpected token: `,`
3734
--> $DIR/offset-of-arg-count.rs:12:21
3835
|
3936
LL | offset_of!(S, f.,);
40-
| ^ expected identifier
37+
| ^
4138

42-
error: no rules expected the token `..`
43-
--> $DIR/offset-of-arg-count.rs:13:20
39+
error: offset_of expects dot-separated field and variant names
40+
--> $DIR/offset-of-arg-count.rs:13:19
4441
|
4542
LL | offset_of!(S, f..);
46-
| ^^ no rules expected this token in macro call
47-
|
48-
= note: while trying to match sequence start
43+
| ^^^
4944

50-
error: no rules expected the token `..`
51-
--> $DIR/offset-of-arg-count.rs:14:20
45+
error: offset_of expects dot-separated field and variant names
46+
--> $DIR/offset-of-arg-count.rs:14:19
5247
|
5348
LL | offset_of!(S, f..,);
54-
| ^^ no rules expected this token in macro call
55-
|
56-
= note: while trying to match sequence start
49+
| ^^^
5750

5851
error: aborting due to 7 previous errors
5952

tests/ui/offset-of/offset-of-builtin.rs

+6-18
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,25 @@ fn main() {
88
builtin # offset_of(NotEnoughArguments); //~ ERROR expected one of
99
}
1010
fn t1() {
11-
// Already errored upon at the macro level. Yielding an error would require
12-
// extra effort.
13-
builtin # offset_of(NotEnoughArgumentsWithAComma, );
11+
builtin # offset_of(NotEnoughArgumentsWithAComma, ); //~ ERROR expected expression
1412
}
1513
fn t2() {
16-
builtin # offset_of(Container, field, too many arguments); //~ ERROR expected identifier, found
17-
//~| ERROR found `,`
18-
//~| ERROR found `many`
19-
//~| ERROR found `arguments`
14+
builtin # offset_of(S, f, too many arguments); //~ ERROR expected `)`, found `too`
2015
}
2116
fn t3() {
2217
builtin # offset_of(S, f); // compiles fine
2318
}
2419
fn t4() {
25-
// Already errored upon at the macro level. Yielding an error would require
26-
// extra effort.
27-
builtin # offset_of(S, f);
20+
builtin # offset_of(S, f.); //~ ERROR unexpected token
2821
}
2922
fn t5() {
30-
builtin # offset_of(S, f.); //~ ERROR expected identifier
23+
builtin # offset_of(S, f.,); //~ ERROR unexpected token
3124
}
3225
fn t6() {
33-
builtin # offset_of(S, f.,); //~ ERROR expected identifier
26+
builtin # offset_of(S, f..); //~ ERROR offset_of expects dot-separated field and variant names
3427
}
3528
fn t7() {
36-
builtin # offset_of(S, f..); //~ ERROR expected one of
37-
}
38-
fn t8() {
39-
// Already errored upon at the macro level. Yielding an error would require
40-
// extra effort.
41-
builtin # offset_of(S, f..,);
29+
builtin # offset_of(S, f..,); //~ ERROR offset_of expects dot-separated field and variant names
4230
}
4331

4432
struct S { f: u8, }

0 commit comments

Comments
 (0)