From 8d824c7828c1abfb9082a5ad884dc511e9070ebc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 22 Apr 2025 00:33:06 +0300 Subject: [PATCH] Resolve `offset_of!()` in IDE --- crates/hir/src/semantics.rs | 7 +++ crates/hir/src/source_analyzer.rs | 79 +++++++++++++++++++++++++++++-- crates/ide-db/src/defs.rs | 8 ++++ crates/ide/src/goto_definition.rs | 70 +++++++++++++++++++++++++++ crates/test-utils/src/minicore.rs | 8 ++++ 5 files changed, 169 insertions(+), 3 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 0e5da85606..91374d1f6a 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 b2ca2c6c4d..5253afe949 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}, @@ -791,6 +792,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/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index 4a2896ab14..ed96efbef9 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 {