diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 48191d15b2..f708f2e166 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1622,6 +1622,13 @@ impl<'db> SemanticsImpl<'db> { self.analyze(name.syntax())?.resolve_use_type_arg(name) } + pub fn resolve_offset_of_field( + &self, + name_ref: &ast::NameRef, + ) -> Option<(Either, GenericSubstitution)> { + self.analyze_no_infer(name_ref.syntax())?.resolve_offset_of_field(self.db, name_ref) + } + pub fn resolve_mod_path( &self, scope: &SyntaxNode, diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 8c35defbf9..108c8f0e18 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -14,7 +14,7 @@ use crate::{ }; use either::Either; use hir_def::{ - AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId, + AdtId, AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId, expr_store::{ Body, BodySourceMap, ExpressionStore, ExpressionStoreSourceMap, HygieneId, @@ -34,8 +34,8 @@ use hir_expand::{ name::{AsName, Name}, }; use hir_ty::{ - Adjustment, InferenceResult, Interner, Substitution, TraitEnvironment, Ty, TyExt, TyKind, - TyLoweringContext, + Adjustment, AliasTy, InferenceResult, Interner, ProjectionTy, Substitution, TraitEnvironment, + Ty, TyExt, TyKind, TyLoweringContext, diagnostics::{ InsideUnsafeBlock, record_literal_missing_fields, record_pattern_missing_fields, unsafe_operations, @@ -47,6 +47,7 @@ use hir_ty::{ use intern::sym; use itertools::Itertools; use smallvec::SmallVec; +use stdx::never; use syntax::{ SyntaxKind, SyntaxNode, TextRange, TextSize, ast::{self, AstNode, RangeItem, RangeOp}, @@ -788,6 +789,78 @@ impl SourceAnalyzer { .map(crate::TypeParam::from) } + pub(crate) fn resolve_offset_of_field( + &self, + db: &dyn HirDatabase, + name_ref: &ast::NameRef, + ) -> Option<(Either, GenericSubstitution)> { + let offset_of_expr = ast::OffsetOfExpr::cast(name_ref.syntax().parent()?)?; + let container = offset_of_expr.ty()?; + let container = self.type_of_type(db, &container)?; + + let trait_env = container.env; + let mut container = Either::Right(container.ty); + for field_name in offset_of_expr.fields() { + if let Some( + TyKind::Alias(AliasTy::Projection(ProjectionTy { associated_ty_id, substitution })) + | TyKind::AssociatedType(associated_ty_id, substitution), + ) = container.as_ref().right().map(|it| it.kind(Interner)) + { + let projection = ProjectionTy { + associated_ty_id: *associated_ty_id, + substitution: substitution.clone(), + }; + container = Either::Right(db.normalize_projection(projection, trait_env.clone())); + } + let handle_variants = |variant, subst: &Substitution, container: &mut _| { + let fields = db.variant_fields(variant); + let field = fields.field(&field_name.as_name())?; + let field_types = db.field_types(variant); + *container = Either::Right(field_types[field].clone().substitute(Interner, subst)); + let generic_def = match variant { + VariantId::EnumVariantId(it) => it.loc(db).parent.into(), + VariantId::StructId(it) => it.into(), + VariantId::UnionId(it) => it.into(), + }; + Some(( + Either::Right(Field { parent: variant.into(), id: field }), + generic_def, + subst.clone(), + )) + }; + let temp_ty = TyKind::Error.intern(Interner); + let (field_def, generic_def, subst) = + match std::mem::replace(&mut container, Either::Right(temp_ty.clone())) { + Either::Left((variant_id, subst)) => { + handle_variants(VariantId::from(variant_id), &subst, &mut container)? + } + Either::Right(container_ty) => match container_ty.kind(Interner) { + TyKind::Adt(adt_id, subst) => match adt_id.0 { + AdtId::StructId(id) => { + handle_variants(id.into(), subst, &mut container)? + } + AdtId::UnionId(id) => { + handle_variants(id.into(), subst, &mut container)? + } + AdtId::EnumId(id) => { + let variants = db.enum_variants(id); + let variant = variants.variant(&field_name.as_name())?; + container = Either::Left((variant, subst.clone())); + (Either::Left(Variant { id: variant }), id.into(), subst.clone()) + } + }, + _ => return None, + }, + }; + + if field_name.syntax().text_range() == name_ref.syntax().text_range() { + return Some((field_def, GenericSubstitution::new(generic_def, subst, trait_env))); + } + } + never!("the `NameRef` is a child of the `OffsetOfExpr`, we should've visited it"); + None + } + pub(crate) fn resolve_path( &self, db: &dyn HirDatabase, diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs index 783fbd93f1..bf4f541ff5 100644 --- a/crates/ide-db/src/defs.rs +++ b/crates/ide-db/src/defs.rs @@ -839,6 +839,14 @@ impl NameRefClass { ast::AsmRegSpec(_) => { Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(()), None)) }, + ast::OffsetOfExpr(_) => { + let (def, subst) = sema.resolve_offset_of_field(name_ref)?; + let def = match def { + Either::Left(variant) => Definition::Variant(variant), + Either::Right(field) => Definition::Field(field), + }; + Some(NameRefClass::Definition(def, Some(subst))) + }, _ => None } } diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index ebd68983ed..b894e85752 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -3476,4 +3476,74 @@ fn main() { "#, ); } + + #[test] + fn offset_of() { + check( + r#" +//- minicore: offset_of +struct Foo { + field: i32, + // ^^^^^ +} + +fn foo() { + let _ = core::mem::offset_of!(Foo, fiel$0d); +} + "#, + ); + + check( + r#" +//- minicore: offset_of +struct Bar(Foo); +struct Foo { + field: i32, + // ^^^^^ +} + +fn foo() { + let _ = core::mem::offset_of!(Bar, 0.fiel$0d); +} + "#, + ); + + check( + r#" +//- minicore: offset_of +struct Bar(Baz); +enum Baz { + Abc(Foo), + None, +} +struct Foo { + field: i32, + // ^^^^^ +} + +fn foo() { + let _ = core::mem::offset_of!(Bar, 0.Abc.0.fiel$0d); +} + "#, + ); + + check( + r#" +//- minicore: offset_of +struct Bar(Baz); +enum Baz { + Abc(Foo), + // ^^^ + None, +} +struct Foo { + field: i32, +} + +fn foo() { + let _ = core::mem::offset_of!(Bar, 0.Ab$0c.0.field); +} + "#, + ); + } } diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index 407320e1d0..c66afed91c 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -258,6 +258,15 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { p.expect(T!['(']); type_(p); p.expect(T![,]); + // Due to our incomplete handling of macro groups, especially + // those with empty delimiters, we wrap `expr` fragments in + // parentheses sometimes. Since `offset_of` is a macro, and takes + // `expr`, the field names could be wrapped in parentheses. + let wrapped_in_parens = p.eat(T!['(']); + // test offset_of_parens + // fn foo() { + // builtin#offset_of(Foo, (bar.baz.0)); + // } while !p.at(EOF) && !p.at(T![')']) { name_ref_mod_path_or_index(p); if !p.at(T![')']) { @@ -265,6 +274,9 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { } } p.expect(T![')']); + if wrapped_in_parens { + p.expect(T![')']); + } Some(m.complete(p, OFFSET_OF_EXPR)) } else if p.at_contextual_kw(T![format_args]) { p.bump_remap(T![format_args]); diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 87ffc99539..6c9d02aaa8 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -422,6 +422,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/nocontentexpr_after_item.rs"); } #[test] + fn offset_of_parens() { + run_and_expect_no_errors("test_data/parser/inline/ok/offset_of_parens.rs"); + } + #[test] fn or_pattern() { run_and_expect_no_errors("test_data/parser/inline/ok/or_pattern.rs"); } #[test] fn param_list() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list.rs"); } diff --git a/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast new file mode 100644 index 0000000000..4e23455cfc --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast @@ -0,0 +1,42 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + OFFSET_OF_EXPR + BUILTIN_KW "builtin" + POUND "#" + OFFSET_OF_KW "offset_of" + L_PAREN "(" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + COMMA "," + WHITESPACE " " + L_PAREN "(" + NAME_REF + IDENT "bar" + DOT "." + NAME_REF + IDENT "baz" + DOT "." + NAME_REF + INT_NUMBER "0" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs new file mode 100644 index 0000000000..a797d5c820 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs @@ -0,0 +1,3 @@ +fn foo() { + builtin#offset_of(Foo, (bar.baz.0)); +} diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 1079dec043..4bdd791eb1 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -70,6 +70,7 @@ //! unimplemented: panic //! column: //! addr_of: +//! offset_of: #![rustc_coherence_is_core] @@ -414,6 +415,13 @@ pub mod mem { use crate::marker::DiscriminantKind; pub struct Discriminant(::Discriminant); // endregion:discriminant + + // region:offset_of + pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { + // The `{}` is for better error messages + {builtin # offset_of($Container, $($fields)+)} + } + // endregion:offset_of } pub mod ptr {