Skip to content

Commit b56765c

Browse files
authored
Merge pull request rust-lang#19657 from ChayimFriedman2/better-offset-of
feat: Better support `offset_of!()`
2 parents 7b249d2 + 5c6f098 commit b56765c

File tree

9 files changed

+230
-3
lines changed

9 files changed

+230
-3
lines changed

src/tools/rust-analyzer/crates/hir/src/semantics.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,6 +1622,13 @@ impl<'db> SemanticsImpl<'db> {
16221622
self.analyze(name.syntax())?.resolve_use_type_arg(name)
16231623
}
16241624

1625+
pub fn resolve_offset_of_field(
1626+
&self,
1627+
name_ref: &ast::NameRef,
1628+
) -> Option<(Either<Variant, Field>, GenericSubstitution)> {
1629+
self.analyze_no_infer(name_ref.syntax())?.resolve_offset_of_field(self.db, name_ref)
1630+
}
1631+
16251632
pub fn resolve_mod_path(
16261633
&self,
16271634
scope: &SyntaxNode,

src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
};
1515
use either::Either;
1616
use hir_def::{
17-
AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId,
17+
AdtId, AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId,
1818
ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId,
1919
expr_store::{
2020
Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, HygieneId,
@@ -34,8 +34,8 @@ use hir_expand::{
3434
name::{AsName, Name},
3535
};
3636
use hir_ty::{
37-
Adjustment, InferenceResult, Interner, Substitution, TraitEnvironment, Ty, TyExt, TyKind,
38-
TyLoweringContext,
37+
Adjustment, AliasTy, InferenceResult, Interner, ProjectionTy, Substitution, TraitEnvironment,
38+
Ty, TyExt, TyKind, TyLoweringContext,
3939
diagnostics::{
4040
InsideUnsafeBlock, record_literal_missing_fields, record_pattern_missing_fields,
4141
unsafe_operations,
@@ -47,6 +47,7 @@ use hir_ty::{
4747
use intern::sym;
4848
use itertools::Itertools;
4949
use smallvec::SmallVec;
50+
use stdx::never;
5051
use syntax::{
5152
SyntaxKind, SyntaxNode, TextRange, TextSize,
5253
ast::{self, AstNode, RangeItem, RangeOp},
@@ -788,6 +789,78 @@ impl SourceAnalyzer {
788789
.map(crate::TypeParam::from)
789790
}
790791

792+
pub(crate) fn resolve_offset_of_field(
793+
&self,
794+
db: &dyn HirDatabase,
795+
name_ref: &ast::NameRef,
796+
) -> Option<(Either<crate::Variant, crate::Field>, GenericSubstitution)> {
797+
let offset_of_expr = ast::OffsetOfExpr::cast(name_ref.syntax().parent()?)?;
798+
let container = offset_of_expr.ty()?;
799+
let container = self.type_of_type(db, &container)?;
800+
801+
let trait_env = container.env;
802+
let mut container = Either::Right(container.ty);
803+
for field_name in offset_of_expr.fields() {
804+
if let Some(
805+
TyKind::Alias(AliasTy::Projection(ProjectionTy { associated_ty_id, substitution }))
806+
| TyKind::AssociatedType(associated_ty_id, substitution),
807+
) = container.as_ref().right().map(|it| it.kind(Interner))
808+
{
809+
let projection = ProjectionTy {
810+
associated_ty_id: *associated_ty_id,
811+
substitution: substitution.clone(),
812+
};
813+
container = Either::Right(db.normalize_projection(projection, trait_env.clone()));
814+
}
815+
let handle_variants = |variant, subst: &Substitution, container: &mut _| {
816+
let fields = db.variant_fields(variant);
817+
let field = fields.field(&field_name.as_name())?;
818+
let field_types = db.field_types(variant);
819+
*container = Either::Right(field_types[field].clone().substitute(Interner, subst));
820+
let generic_def = match variant {
821+
VariantId::EnumVariantId(it) => it.loc(db).parent.into(),
822+
VariantId::StructId(it) => it.into(),
823+
VariantId::UnionId(it) => it.into(),
824+
};
825+
Some((
826+
Either::Right(Field { parent: variant.into(), id: field }),
827+
generic_def,
828+
subst.clone(),
829+
))
830+
};
831+
let temp_ty = TyKind::Error.intern(Interner);
832+
let (field_def, generic_def, subst) =
833+
match std::mem::replace(&mut container, Either::Right(temp_ty.clone())) {
834+
Either::Left((variant_id, subst)) => {
835+
handle_variants(VariantId::from(variant_id), &subst, &mut container)?
836+
}
837+
Either::Right(container_ty) => match container_ty.kind(Interner) {
838+
TyKind::Adt(adt_id, subst) => match adt_id.0 {
839+
AdtId::StructId(id) => {
840+
handle_variants(id.into(), subst, &mut container)?
841+
}
842+
AdtId::UnionId(id) => {
843+
handle_variants(id.into(), subst, &mut container)?
844+
}
845+
AdtId::EnumId(id) => {
846+
let variants = db.enum_variants(id);
847+
let variant = variants.variant(&field_name.as_name())?;
848+
container = Either::Left((variant, subst.clone()));
849+
(Either::Left(Variant { id: variant }), id.into(), subst.clone())
850+
}
851+
},
852+
_ => return None,
853+
},
854+
};
855+
856+
if field_name.syntax().text_range() == name_ref.syntax().text_range() {
857+
return Some((field_def, GenericSubstitution::new(generic_def, subst, trait_env)));
858+
}
859+
}
860+
never!("the `NameRef` is a child of the `OffsetOfExpr`, we should've visited it");
861+
None
862+
}
863+
791864
pub(crate) fn resolve_path(
792865
&self,
793866
db: &dyn HirDatabase,

src/tools/rust-analyzer/crates/ide-db/src/defs.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,14 @@ impl NameRefClass {
839839
ast::AsmRegSpec(_) => {
840840
Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(()), None))
841841
},
842+
ast::OffsetOfExpr(_) => {
843+
let (def, subst) = sema.resolve_offset_of_field(name_ref)?;
844+
let def = match def {
845+
Either::Left(variant) => Definition::Variant(variant),
846+
Either::Right(field) => Definition::Field(field),
847+
};
848+
Some(NameRefClass::Definition(def, Some(subst)))
849+
},
842850
_ => None
843851
}
844852
}

src/tools/rust-analyzer/crates/ide/src/goto_definition.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3476,4 +3476,74 @@ fn main() {
34763476
"#,
34773477
);
34783478
}
3479+
3480+
#[test]
3481+
fn offset_of() {
3482+
check(
3483+
r#"
3484+
//- minicore: offset_of
3485+
struct Foo {
3486+
field: i32,
3487+
// ^^^^^
3488+
}
3489+
3490+
fn foo() {
3491+
let _ = core::mem::offset_of!(Foo, fiel$0d);
3492+
}
3493+
"#,
3494+
);
3495+
3496+
check(
3497+
r#"
3498+
//- minicore: offset_of
3499+
struct Bar(Foo);
3500+
struct Foo {
3501+
field: i32,
3502+
// ^^^^^
3503+
}
3504+
3505+
fn foo() {
3506+
let _ = core::mem::offset_of!(Bar, 0.fiel$0d);
3507+
}
3508+
"#,
3509+
);
3510+
3511+
check(
3512+
r#"
3513+
//- minicore: offset_of
3514+
struct Bar(Baz);
3515+
enum Baz {
3516+
Abc(Foo),
3517+
None,
3518+
}
3519+
struct Foo {
3520+
field: i32,
3521+
// ^^^^^
3522+
}
3523+
3524+
fn foo() {
3525+
let _ = core::mem::offset_of!(Bar, 0.Abc.0.fiel$0d);
3526+
}
3527+
"#,
3528+
);
3529+
3530+
check(
3531+
r#"
3532+
//- minicore: offset_of
3533+
struct Bar(Baz);
3534+
enum Baz {
3535+
Abc(Foo),
3536+
// ^^^
3537+
None,
3538+
}
3539+
struct Foo {
3540+
field: i32,
3541+
}
3542+
3543+
fn foo() {
3544+
let _ = core::mem::offset_of!(Bar, 0.Ab$0c.0.field);
3545+
}
3546+
"#,
3547+
);
3548+
}
34793549
}

src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,25 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
258258
p.expect(T!['(']);
259259
type_(p);
260260
p.expect(T![,]);
261+
// Due to our incomplete handling of macro groups, especially
262+
// those with empty delimiters, we wrap `expr` fragments in
263+
// parentheses sometimes. Since `offset_of` is a macro, and takes
264+
// `expr`, the field names could be wrapped in parentheses.
265+
let wrapped_in_parens = p.eat(T!['(']);
266+
// test offset_of_parens
267+
// fn foo() {
268+
// builtin#offset_of(Foo, (bar.baz.0));
269+
// }
261270
while !p.at(EOF) && !p.at(T![')']) {
262271
name_ref_mod_path_or_index(p);
263272
if !p.at(T![')']) {
264273
p.expect(T![.]);
265274
}
266275
}
267276
p.expect(T![')']);
277+
if wrapped_in_parens {
278+
p.expect(T![')']);
279+
}
268280
Some(m.complete(p, OFFSET_OF_EXPR))
269281
} else if p.at_contextual_kw(T![format_args]) {
270282
p.bump_remap(T![format_args]);

src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ mod ok {
422422
run_and_expect_no_errors("test_data/parser/inline/ok/nocontentexpr_after_item.rs");
423423
}
424424
#[test]
425+
fn offset_of_parens() {
426+
run_and_expect_no_errors("test_data/parser/inline/ok/offset_of_parens.rs");
427+
}
428+
#[test]
425429
fn or_pattern() { run_and_expect_no_errors("test_data/parser/inline/ok/or_pattern.rs"); }
426430
#[test]
427431
fn param_list() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list.rs"); }
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
SOURCE_FILE
2+
FN
3+
FN_KW "fn"
4+
WHITESPACE " "
5+
NAME
6+
IDENT "foo"
7+
PARAM_LIST
8+
L_PAREN "("
9+
R_PAREN ")"
10+
WHITESPACE " "
11+
BLOCK_EXPR
12+
STMT_LIST
13+
L_CURLY "{"
14+
WHITESPACE "\n "
15+
EXPR_STMT
16+
OFFSET_OF_EXPR
17+
BUILTIN_KW "builtin"
18+
POUND "#"
19+
OFFSET_OF_KW "offset_of"
20+
L_PAREN "("
21+
PATH_TYPE
22+
PATH
23+
PATH_SEGMENT
24+
NAME_REF
25+
IDENT "Foo"
26+
COMMA ","
27+
WHITESPACE " "
28+
L_PAREN "("
29+
NAME_REF
30+
IDENT "bar"
31+
DOT "."
32+
NAME_REF
33+
IDENT "baz"
34+
DOT "."
35+
NAME_REF
36+
INT_NUMBER "0"
37+
R_PAREN ")"
38+
R_PAREN ")"
39+
SEMICOLON ";"
40+
WHITESPACE "\n"
41+
R_CURLY "}"
42+
WHITESPACE "\n"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn foo() {
2+
builtin#offset_of(Foo, (bar.baz.0));
3+
}

src/tools/rust-analyzer/crates/test-utils/src/minicore.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
//! unimplemented: panic
7171
//! column:
7272
//! addr_of:
73+
//! offset_of:
7374
7475
#![rustc_coherence_is_core]
7576

@@ -414,6 +415,13 @@ pub mod mem {
414415
use crate::marker::DiscriminantKind;
415416
pub struct Discriminant<T>(<T as DiscriminantKind>::Discriminant);
416417
// endregion:discriminant
418+
419+
// region:offset_of
420+
pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) {
421+
// The `{}` is for better error messages
422+
{builtin # offset_of($Container, $($fields)+)}
423+
}
424+
// endregion:offset_of
417425
}
418426

419427
pub mod ptr {

0 commit comments

Comments
 (0)