Skip to content

Compute data layout of types #13490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 7, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/hir-def/Cargo.toml
Original file line number Diff line number Diff line change
@@ -33,6 +33,8 @@ base-db = { path = "../base-db", version = "0.0.0" }
syntax = { path = "../syntax", version = "0.0.0" }
profile = { path = "../profile", version = "0.0.0" }
hir-expand = { path = "../hir-expand", version = "0.0.0" }
rustc_abi = { version = "0.0.20221125", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false }
rustc_index = { version = "0.0.20221125", package = "hkalbasi-rustc-ap-rustc_index", default-features = false }
mbe = { path = "../mbe", version = "0.0.0" }
cfg = { path = "../cfg", version = "0.0.0" }
tt = { path = "../tt", version = "0.0.0" }
95 changes: 54 additions & 41 deletions crates/hir-def/src/adt.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Defines hir-level representation of structs, enums and unions

use std::{num::NonZeroU32, sync::Arc};
use std::sync::Arc;

use base_db::CrateId;
use either::Either;
@@ -9,6 +9,7 @@ use hir_expand::{
HirFileId, InFile,
};
use la_arena::{Arena, ArenaMap};
use rustc_abi::{Integer, IntegerType};
use syntax::ast::{self, HasName, HasVisibility};
use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};

@@ -18,6 +19,7 @@ use crate::{
db::DefDatabase,
intern::Interned,
item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
layout::{Align, ReprFlags, ReprOptions},
nameres::diagnostics::DefDiagnostic,
src::HasChildSource,
src::HasSource,
@@ -34,15 +36,15 @@ use cfg::CfgOptions;
pub struct StructData {
pub name: Name,
pub variant_data: Arc<VariantData>,
pub repr: Option<ReprData>,
pub repr: Option<ReprOptions>,
pub visibility: RawVisibility,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumData {
pub name: Name,
pub variants: Arena<EnumVariantData>,
pub repr: Option<ReprData>,
pub repr: Option<ReprOptions>,
pub visibility: RawVisibility,
}

@@ -67,80 +69,91 @@ pub struct FieldData {
pub visibility: RawVisibility,
}

#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub enum ReprKind {
C,
BuiltinInt { builtin: Either<BuiltinInt, BuiltinUint>, is_c: bool },
Transparent,
Default,
}

#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub struct ReprData {
pub kind: ReprKind,
pub packed: bool,
pub align: Option<NonZeroU32>,
}

fn repr_from_value(
db: &dyn DefDatabase,
krate: CrateId,
item_tree: &ItemTree,
of: AttrOwner,
) -> Option<ReprData> {
) -> Option<ReprOptions> {
item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt)
}

fn parse_repr_tt(tt: &Subtree) -> Option<ReprData> {
fn parse_repr_tt(tt: &Subtree) -> Option<ReprOptions> {
match tt.delimiter {
Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {}
_ => return None,
}

let mut data = ReprData { kind: ReprKind::Default, packed: false, align: None };
let mut flags = ReprFlags::empty();
let mut int = None;
let mut max_align: Option<Align> = None;
let mut min_pack: Option<Align> = None;

let mut tts = tt.token_trees.iter().peekable();
while let Some(tt) = tts.next() {
if let TokenTree::Leaf(Leaf::Ident(ident)) = tt {
match &*ident.text {
flags.insert(match &*ident.text {
"packed" => {
data.packed = true;
if let Some(TokenTree::Subtree(_)) = tts.peek() {
let pack = if let Some(TokenTree::Subtree(tt)) = tts.peek() {
tts.next();
}
if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
lit.text.parse().unwrap_or_default()
} else {
0
}
} else {
0
};
let pack = Align::from_bytes(pack).unwrap();
min_pack =
Some(if let Some(min_pack) = min_pack { min_pack.min(pack) } else { pack });
ReprFlags::empty()
}
"align" => {
if let Some(TokenTree::Subtree(tt)) = tts.peek() {
tts.next();
if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
if let Ok(align) = lit.text.parse() {
data.align = Some(align);
let align = Align::from_bytes(align).ok();
max_align = max_align.max(align);
}
}
}
ReprFlags::empty()
}
"C" => {
if let ReprKind::BuiltinInt { is_c, .. } = &mut data.kind {
*is_c = true;
} else {
data.kind = ReprKind::C;
}
}
"transparent" => data.kind = ReprKind::Transparent,
"C" => ReprFlags::IS_C,
"transparent" => ReprFlags::IS_TRANSPARENT,
repr => {
let is_c = matches!(data.kind, ReprKind::C);
if let Some(builtin) = BuiltinInt::from_suffix(repr)
.map(Either::Left)
.or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
{
data.kind = ReprKind::BuiltinInt { builtin, is_c };
int = Some(match builtin {
Either::Left(bi) => match bi {
BuiltinInt::Isize => IntegerType::Pointer(true),
BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true),
BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true),
BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true),
BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true),
BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true),
},
Either::Right(bu) => match bu {
BuiltinUint::Usize => IntegerType::Pointer(false),
BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false),
BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false),
BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false),
BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false),
BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false),
},
});
}
ReprFlags::empty()
}
}
})
}
}

Some(data)
Some(ReprOptions { int, align: max_align, pack: min_pack, flags, field_shuffle_seed: 0 })
}

impl StructData {
@@ -281,10 +294,10 @@ impl EnumData {
Some(id)
}

pub fn variant_body_type(&self) -> Either<BuiltinInt, BuiltinUint> {
pub fn variant_body_type(&self) -> IntegerType {
match self.repr {
Some(ReprData { kind: ReprKind::BuiltinInt { builtin, .. }, .. }) => builtin,
_ => Either::Left(BuiltinInt::Isize),
Some(ReprOptions { int: Some(builtin), .. }) => builtin,
_ => IntegerType::Pointer(true),
}
}
}
96 changes: 96 additions & 0 deletions crates/hir-def/src/layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Definitions needed for computing data layout of types.

use std::cmp;

use la_arena::{Idx, RawIdx};
pub use rustc_abi::{
Abi, AbiAndPrefAlign, AddressSpace, Align, Endian, FieldsShape, Integer, IntegerType,
LayoutCalculator, Niche, Primitive, ReprFlags, ReprOptions, Scalar, Size, StructKind,
TargetDataLayout, WrappingRange,
};

use crate::LocalEnumVariantId;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RustcEnumVariantIdx(pub LocalEnumVariantId);

impl rustc_index::vec::Idx for RustcEnumVariantIdx {
fn new(idx: usize) -> Self {
RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32)))
}

fn index(self) -> usize {
u32::from(self.0.into_raw()) as usize
}
}

pub type Layout = rustc_abi::LayoutS<RustcEnumVariantIdx>;
pub type TagEncoding = rustc_abi::TagEncoding<RustcEnumVariantIdx>;
pub type Variants = rustc_abi::Variants<RustcEnumVariantIdx>;

pub trait IntegerExt {
fn repr_discr(
dl: &TargetDataLayout,
repr: &ReprOptions,
min: i128,
max: i128,
) -> Result<(Integer, bool), LayoutError>;
}

impl IntegerExt for Integer {
/// Finds the appropriate Integer type and signedness for the given
/// signed discriminant range and `#[repr]` attribute.
/// N.B.: `u128` values above `i128::MAX` will be treated as signed, but
/// that shouldn't affect anything, other than maybe debuginfo.
fn repr_discr(
dl: &TargetDataLayout,
repr: &ReprOptions,
min: i128,
max: i128,
) -> Result<(Integer, bool), LayoutError> {
// Theoretically, negative values could be larger in unsigned representation
// than the unsigned representation of the signed minimum. However, if there
// are any negative values, the only valid unsigned representation is u128
// which can fit all i128 values, so the result remains unaffected.
let unsigned_fit = Integer::fit_unsigned(cmp::max(min as u128, max as u128));
let signed_fit = cmp::max(Integer::fit_signed(min), Integer::fit_signed(max));

if let Some(ity) = repr.int {
let discr = Integer::from_attr(dl, ity);
let fit = if ity.is_signed() { signed_fit } else { unsigned_fit };
if discr < fit {
return Err(LayoutError::UserError(
"Integer::repr_discr: `#[repr]` hint too small for \
discriminant range of enum "
.to_string(),
));
}
return Ok((discr, ity.is_signed()));
}

let at_least = if repr.c() {
// This is usually I32, however it can be different on some platforms,
// notably hexagon and arm-none/thumb-none
dl.c_enum_min_size
} else {
// repr(Rust) enums try to be as small as possible
Integer::I8
};

// If there are no negative values, we can use the unsigned fit.
Ok(if min >= 0 {
(cmp::max(unsigned_fit, at_least), false)
} else {
(cmp::max(signed_fit, at_least), true)
})
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum LayoutError {
UserError(String),
SizeOverflow,
HasPlaceholder,
NotImplemented,
Unknown,
}
1 change: 1 addition & 0 deletions crates/hir-def/src/lib.rs
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ pub mod adt;
pub mod data;
pub mod generics;
pub mod lang_item;
pub mod layout;

pub mod expr;
pub mod body;
1 change: 1 addition & 0 deletions crates/hir-expand/src/name.rs
Original file line number Diff line number Diff line change
@@ -419,6 +419,7 @@ pub mod known {
shr,
sub_assign,
sub,
unsafe_cell,
va_list
);

1 change: 1 addition & 0 deletions crates/hir-ty/Cargo.toml
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ chalk-derive = "0.87.0"
la-arena = { version = "0.3.0", path = "../../lib/la-arena" }
once_cell = "1.15.0"
typed-arena = "2.0.1"
rustc_index = { version = "0.0.20221125", package = "hkalbasi-rustc-ap-rustc_index", default-features = false }

stdx = { path = "../stdx", version = "0.0.0" }
hir-def = { path = "../hir-def", version = "0.0.0" }
16 changes: 13 additions & 3 deletions crates/hir-ty/src/db.rs
Original file line number Diff line number Diff line change
@@ -6,8 +6,11 @@ use std::sync::Arc;
use arrayvec::ArrayVec;
use base_db::{impl_intern_key, salsa, CrateId, Upcast};
use hir_def::{
db::DefDatabase, expr::ExprId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId,
FunctionId, GenericDefId, ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId,
db::DefDatabase,
expr::ExprId,
layout::{Layout, LayoutError, TargetDataLayout},
AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumVariantId, FunctionId, GenericDefId,
ImplId, LifetimeParamId, LocalFieldId, TypeOrConstParamId, VariantId,
};
use la_arena::ArenaMap;

@@ -16,7 +19,7 @@ use crate::{
consteval::{ComputedExpr, ConstEvalError},
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
Binders, CallableDefId, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, PolyFnSig,
QuantifiedWhereClause, ReturnTypeImplTraits, TraitRef, Ty, TyDefId, ValueTyDefId,
QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, ValueTyDefId,
};
use hir_expand::name::Name;

@@ -57,6 +60,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::lower::field_types_query)]
fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalFieldId, Binders<Ty>>>;

#[salsa::invoke(crate::layout::layout_of_adt_query)]
#[salsa::cycle(crate::layout::layout_of_adt_recover)]
fn layout_of_adt(&self, def: AdtId, subst: Substitution) -> Result<Layout, LayoutError>;

#[salsa::invoke(crate::layout::current_target_data_layout_query)]
fn current_target_data_layout(&self) -> Arc<TargetDataLayout>;

#[salsa::invoke(crate::lower::callable_item_sig)]
fn callable_item_signature(&self, def: CallableDefId) -> PolyFnSig;

13 changes: 3 additions & 10 deletions crates/hir-ty/src/diagnostics/match_check.rs
Original file line number Diff line number Diff line change
@@ -12,16 +12,16 @@ pub(crate) mod usefulness;

use chalk_ir::Mutability;
use hir_def::{
adt::VariantData, body::Body, expr::PatId, AdtId, EnumVariantId, HasModule, LocalFieldId,
VariantId,
adt::VariantData, body::Body, expr::PatId, AdtId, EnumVariantId, LocalFieldId, VariantId,
};
use hir_expand::name::{name, Name};
use hir_expand::name::Name;
use stdx::{always, never};

use crate::{
db::HirDatabase,
display::{HirDisplay, HirDisplayError, HirFormatter},
infer::BindingMode,
lang_items::is_box,
InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
};

@@ -405,13 +405,6 @@ where
}
}

fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool {
let owned_box = name![owned_box].to_smol_str();
let krate = adt.module(db.upcast()).krate();
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from);
Some(adt) == box_adt
}

pub(crate) trait PatternFoldable: Sized {
fn fold_with<F: PatternFolder>(&self, folder: &mut F) -> Self {
self.super_fold_with(folder)
25 changes: 22 additions & 3 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
@@ -19,10 +19,11 @@ use std::sync::Arc;
use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags};
use hir_def::{
body::Body,
builtin_type::BuiltinType,
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
data::{ConstData, StaticData},
expr::{BindingAnnotation, ExprId, PatId},
lang_item::LangItemTarget,
layout::Integer,
path::{path, Path},
resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs},
type_ref::TypeRef,
@@ -70,8 +71,26 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)),
DefWithBodyId::VariantId(v) => {
ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() {
Either::Left(builtin) => BuiltinType::Int(builtin),
Either::Right(builtin) => BuiltinType::Uint(builtin),
hir_def::layout::IntegerType::Pointer(signed) => match signed {
true => BuiltinType::Int(BuiltinInt::Isize),
false => BuiltinType::Uint(BuiltinUint::Usize),
},
hir_def::layout::IntegerType::Fixed(size, signed) => match signed {
true => BuiltinType::Int(match size {
Integer::I8 => BuiltinInt::I8,
Integer::I16 => BuiltinInt::I16,
Integer::I32 => BuiltinInt::I32,
Integer::I64 => BuiltinInt::I64,
Integer::I128 => BuiltinInt::I128,
}),
false => BuiltinType::Uint(match size {
Integer::I8 => BuiltinUint::U8,
Integer::I16 => BuiltinUint::U16,
Integer::I32 => BuiltinUint::U32,
Integer::I64 => BuiltinUint::U64,
Integer::I128 => BuiltinUint::U128,
}),
},
});
}
}
20 changes: 20 additions & 0 deletions crates/hir-ty/src/lang_items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Functions to detect special lang items
use hir_def::{AdtId, HasModule};
use hir_expand::name;

use crate::db::HirDatabase;

pub fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool {
let owned_box = name![owned_box].to_smol_str();
let krate = adt.module(db.upcast()).krate();
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from);
Some(adt) == box_adt
}

pub fn is_unsafe_cell(adt: AdtId, db: &dyn HirDatabase) -> bool {
let owned_box = name![unsafe_cell].to_smol_str();
let krate = adt.module(db.upcast()).krate();
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from);
Some(adt) == box_adt
}
272 changes: 272 additions & 0 deletions crates/hir-ty/src/layout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
//! Compute the binary representation of a type
use std::sync::Arc;

use chalk_ir::{AdtId, TyKind};
pub(self) use hir_def::layout::*;
use hir_def::LocalFieldId;
use stdx::never;

use crate::{db::HirDatabase, Interner, Substitution, Ty};

use self::adt::struct_variant_idx;
pub use self::{
adt::{layout_of_adt_query, layout_of_adt_recover},
target::current_target_data_layout_query,
};

macro_rules! user_error {
($x: expr) => {
return Err(LayoutError::UserError(format!($x)))
};
}

mod adt;
mod target;

struct LayoutCx<'a> {
db: &'a dyn HirDatabase,
}

impl LayoutCalculator for LayoutCx<'_> {
type TargetDataLayoutRef = Arc<TargetDataLayout>;

fn delay_bug(&self, txt: &str) {
never!("{}", txt);
}

fn current_data_layout(&self) -> Arc<TargetDataLayout> {
self.db.current_target_data_layout()
}
}

fn scalar_unit(dl: &TargetDataLayout, value: Primitive) -> Scalar {
Scalar::Initialized { value, valid_range: WrappingRange::full(value.size(dl)) }
}

fn scalar(dl: &TargetDataLayout, value: Primitive) -> Layout {
Layout::scalar(dl, scalar_unit(dl, value))
}

pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty) -> Result<Layout, LayoutError> {
let dl = &*db.current_target_data_layout();
let cx = LayoutCx { db };
Ok(match ty.kind(Interner) {
TyKind::Adt(AdtId(def), subst) => db.layout_of_adt(*def, subst.clone())?,
TyKind::Scalar(s) => match s {
chalk_ir::Scalar::Bool => Layout::scalar(
dl,
Scalar::Initialized {
value: Primitive::Int(Integer::I8, false),
valid_range: WrappingRange { start: 0, end: 1 },
},
),
chalk_ir::Scalar::Char => Layout::scalar(
dl,
Scalar::Initialized {
value: Primitive::Int(Integer::I32, false),
valid_range: WrappingRange { start: 0, end: 0x10FFFF },
},
),
chalk_ir::Scalar::Int(i) => scalar(
dl,
Primitive::Int(
match i {
chalk_ir::IntTy::Isize => dl.ptr_sized_integer(),
chalk_ir::IntTy::I8 => Integer::I8,
chalk_ir::IntTy::I16 => Integer::I16,
chalk_ir::IntTy::I32 => Integer::I32,
chalk_ir::IntTy::I64 => Integer::I64,
chalk_ir::IntTy::I128 => Integer::I128,
},
false,
),
),
chalk_ir::Scalar::Uint(i) => scalar(
dl,
Primitive::Int(
match i {
chalk_ir::UintTy::Usize => dl.ptr_sized_integer(),
chalk_ir::UintTy::U8 => Integer::I8,
chalk_ir::UintTy::U16 => Integer::I16,
chalk_ir::UintTy::U32 => Integer::I32,
chalk_ir::UintTy::U64 => Integer::I64,
chalk_ir::UintTy::U128 => Integer::I128,
},
true,
),
),
chalk_ir::Scalar::Float(f) => scalar(
dl,
match f {
chalk_ir::FloatTy::F32 => Primitive::F32,
chalk_ir::FloatTy::F64 => Primitive::F64,
},
),
},
TyKind::Tuple(len, tys) => {
let kind = if *len == 0 { StructKind::AlwaysSized } else { StructKind::MaybeUnsized };

let fields = tys
.iter(Interner)
.map(|k| layout_of_ty(db, k.assert_ty_ref(Interner)))
.collect::<Result<Vec<_>, _>>()?;
let fields = fields.iter().collect::<Vec<_>>();
let fields = fields.iter().collect::<Vec<_>>();
cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)?
}
TyKind::Array(element, count) => {
let count = match count.data(Interner).value {
chalk_ir::ConstValue::Concrete(c) => match c.interned {
hir_def::type_ref::ConstScalar::Int(x) => x as u64,
hir_def::type_ref::ConstScalar::UInt(x) => x as u64,
hir_def::type_ref::ConstScalar::Unknown => {
user_error!("unknown const generic parameter")
}
_ => user_error!("mismatched type of const generic parameter"),
},
_ => return Err(LayoutError::HasPlaceholder),
};
let element = layout_of_ty(db, element)?;
let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?;

let abi = if count != 0 && matches!(element.abi, Abi::Uninhabited) {
Abi::Uninhabited
} else {
Abi::Aggregate { sized: true }
};

let largest_niche = if count != 0 { element.largest_niche } else { None };

Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: element.size, count },
abi,
largest_niche,
align: element.align,
size,
}
}
TyKind::Slice(element) => {
let element = layout_of_ty(db, element)?;
Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: element.size, count: 0 },
abi: Abi::Aggregate { sized: false },
largest_niche: None,
align: element.align,
size: Size::ZERO,
}
}
// Potentially-wide pointers.
TyKind::Ref(_, _, pointee) | TyKind::Raw(_, pointee) => {
let mut data_ptr = scalar_unit(dl, Primitive::Pointer);
if matches!(ty.kind(Interner), TyKind::Ref(..)) {
data_ptr.valid_range_mut().start = 1;
}

// let pointee = tcx.normalize_erasing_regions(param_env, pointee);
// if pointee.is_sized(tcx.at(DUMMY_SP), param_env) {
// return Ok(tcx.intern_layout(LayoutS::scalar(cx, data_ptr)));
// }

let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone());
let metadata = match unsized_part.kind(Interner) {
TyKind::Slice(_) | TyKind::Str => {
scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false))
}
TyKind::Dyn(..) => {
let mut vtable = scalar_unit(dl, Primitive::Pointer);
vtable.valid_range_mut().start = 1;
vtable
}
_ => {
// pointee is sized
return Ok(Layout::scalar(dl, data_ptr));
}
};

// Effectively a (ptr, meta) tuple.
cx.scalar_pair(data_ptr, metadata)
}
TyKind::FnDef(_, _) => layout_of_unit(&cx, dl)?,
TyKind::Str => Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Array { stride: Size::from_bytes(1), count: 0 },
abi: Abi::Aggregate { sized: false },
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
},
TyKind::Never => Layout {
variants: Variants::Single { index: struct_variant_idx() },
fields: FieldsShape::Primitive,
abi: Abi::Uninhabited,
largest_niche: None,
align: dl.i8_align,
size: Size::ZERO,
},
TyKind::Dyn(_) | TyKind::Foreign(_) => {
let mut unit = layout_of_unit(&cx, dl)?;
match unit.abi {
Abi::Aggregate { ref mut sized } => *sized = false,
_ => user_error!("bug"),
}
unit
}
TyKind::Function(_) => {
let mut ptr = scalar_unit(dl, Primitive::Pointer);
ptr.valid_range_mut().start = 1;
Layout::scalar(dl, ptr)
}
TyKind::Closure(_, _)
| TyKind::OpaqueType(_, _)
| TyKind::Generator(_, _)
| TyKind::GeneratorWitness(_, _) => return Err(LayoutError::NotImplemented),
TyKind::AssociatedType(_, _)
| TyKind::Error
| TyKind::Alias(_)
| TyKind::Placeholder(_)
| TyKind::BoundVar(_)
| TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder),
})
}

fn layout_of_unit(cx: &LayoutCx<'_>, dl: &TargetDataLayout) -> Result<Layout, LayoutError> {
cx.univariant::<RustcEnumVariantIdx, &&Layout>(
&dl,
&[],
&ReprOptions::default(),
StructKind::AlwaysSized,
)
.ok_or(LayoutError::Unknown)
}

fn struct_tail_erasing_lifetimes(db: &dyn HirDatabase, pointee: Ty) -> Ty {
match pointee.kind(Interner) {
TyKind::Adt(AdtId(adt), subst) => match adt {
&hir_def::AdtId::StructId(i) => {
let data = db.struct_data(i);
let mut it = data.variant_data.fields().iter().rev();
match it.next() {
Some((f, _)) => field_ty(db, i.into(), f, subst),
None => pointee,
}
}
_ => pointee,
},
_ => pointee,
}
}

fn field_ty(
db: &dyn HirDatabase,
def: hir_def::VariantId,
fd: LocalFieldId,
subst: &Substitution,
) -> Ty {
db.field_types(def)[fd].clone().substitute(Interner, subst)
}

#[cfg(test)]
mod tests;
133 changes: 133 additions & 0 deletions crates/hir-ty/src/layout/adt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! Compute the binary representation of structs, unions and enums
use std::ops::Bound;

use hir_def::{
adt::VariantData,
layout::{Integer, IntegerExt, Layout, LayoutCalculator, LayoutError, RustcEnumVariantIdx},
AdtId, EnumVariantId, LocalEnumVariantId, VariantId,
};
use la_arena::RawIdx;
use rustc_index::vec::IndexVec;

use crate::{db::HirDatabase, lang_items::is_unsafe_cell, layout::field_ty, Substitution};

use super::{layout_of_ty, LayoutCx};

pub(crate) fn struct_variant_idx() -> RustcEnumVariantIdx {
RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from(0)))
}

pub fn layout_of_adt_query(
db: &dyn HirDatabase,
def: AdtId,
subst: Substitution,
) -> Result<Layout, LayoutError> {
let dl = db.current_target_data_layout();
let cx = LayoutCx { db };
let handle_variant = |def: VariantId, var: &VariantData| {
var.fields()
.iter()
.map(|(fd, _)| layout_of_ty(db, &field_ty(db, def, fd, &subst)))
.collect::<Result<Vec<_>, _>>()
};
let (variants, is_enum, is_union, repr) = match def {
AdtId::StructId(s) => {
let data = db.struct_data(s);
let mut r = IndexVec::new();
r.push(handle_variant(s.into(), &data.variant_data)?);
(r, false, false, data.repr.unwrap_or_default())
}
AdtId::UnionId(id) => {
let data = db.union_data(id);
let mut r = IndexVec::new();
r.push(handle_variant(id.into(), &data.variant_data)?);
(r, false, true, data.repr.unwrap_or_default())
}
AdtId::EnumId(e) => {
let data = db.enum_data(e);
let r = data
.variants
.iter()
.map(|(idx, v)| {
handle_variant(
EnumVariantId { parent: e, local_id: idx }.into(),
&v.variant_data,
)
})
.collect::<Result<IndexVec<RustcEnumVariantIdx, _>, _>>()?;
(r, true, false, data.repr.unwrap_or_default())
}
};
let variants = variants.iter().map(|x| x.iter().collect::<Vec<_>>()).collect::<Vec<_>>();
let variants = variants.iter().map(|x| x.iter().collect()).collect();
if is_union {
cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown)
} else {
cx.layout_of_struct_or_enum(
&repr,
&variants,
is_enum,
is_unsafe_cell(def, db),
layout_scalar_valid_range(db, def),
|min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)),
variants.iter_enumerated().filter_map(|(id, _)| {
let AdtId::EnumId(e) = def else { return None };
let d = match db
.const_eval_variant(EnumVariantId { parent: e, local_id: id.0 })
.ok()?
{
crate::consteval::ComputedExpr::Literal(l) => match l {
hir_def::expr::Literal::Int(i, _) => i,
hir_def::expr::Literal::Uint(i, _) => i as i128,
_ => return None,
},
_ => return None,
};
Some((id, d))
}),
// FIXME: The current code for niche-filling relies on variant indices
// instead of actual discriminants, so enums with
// explicit discriminants (RFC #2363) would misbehave and we should disable
// niche optimization for them.
// The code that do it in rustc:
// repr.inhibit_enum_layout_opt() || def
// .variants()
// .iter_enumerated()
// .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32()))
repr.inhibit_enum_layout_opt(),
!is_enum
&& variants
.iter()
.next()
.and_then(|x| x.last().map(|x| x.is_unsized()))
.unwrap_or(true),
)
.ok_or(LayoutError::SizeOverflow)
}
}

fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>, Bound<u128>) {
let attrs = db.attrs(def.into());
let get = |name| {
let attr = attrs.by_key(name).tt_values();
for tree in attr {
if let Some(x) = tree.token_trees.first() {
if let Ok(x) = x.to_string().parse() {
return Bound::Included(x);
}
}
}
Bound::Unbounded
};
(get("rustc_layout_scalar_valid_range_start"), get("rustc_layout_scalar_valid_range_end"))
}

pub fn layout_of_adt_recover(
_: &dyn HirDatabase,
_: &[String],
_: &AdtId,
_: &Substitution,
) -> Result<Layout, LayoutError> {
user_error!("infinite sized recursive type");
}
46 changes: 46 additions & 0 deletions crates/hir-ty/src/layout/target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Target dependent parameters needed for layouts
use std::sync::Arc;

use hir_def::layout::TargetDataLayout;

use crate::db::HirDatabase;

use super::{AbiAndPrefAlign, AddressSpace, Align, Endian, Integer, Size};

pub fn current_target_data_layout_query(db: &dyn HirDatabase) -> Arc<TargetDataLayout> {
let crate_graph = db.crate_graph();
let cfg_options = &crate_graph[crate_graph.iter().next().unwrap()].cfg_options;
let endian = match cfg_options.get_cfg_values("target_endian").next() {
Some(x) if x.as_str() == "big" => Endian::Big,
_ => Endian::Little,
};
let pointer_size =
Size::from_bytes(match cfg_options.get_cfg_values("target_pointer_width").next() {
Some(x) => match x.as_str() {
"16" => 2,
"32" => 4,
_ => 8,
},
_ => 8,
});
// FIXME: These values are incorrect for many architectures, at least for aarch64 and riscv64,
// use `rustc +nightly -Z unstable-options --print target-spec-json` or something similar instead.
Arc::new(TargetDataLayout {
endian,
i1_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()),
i8_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()),
i16_align: AbiAndPrefAlign::new(Align::from_bytes(2).unwrap()),
i32_align: AbiAndPrefAlign::new(Align::from_bytes(4).unwrap()),
i64_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()),
i128_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the alignment of i64 and i128 are always 8 Bytes on all platforms. If there's no way to query the correct alignment from the cfg_options, can you add a FIXME comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a way: rustc +nightly -Z unstable-options --print target-spec-json. But it needs nightly. Is it possible to use it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rustc_cfg code already tries to use nightly-only features, and falls back to stable-compatible behavior if those are unavailable or don't work, so this should be doable. However, I think just setting the align to 8 should be fine for now, we can follow up with a fix that asks rustc later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least aarch64 and riscv64 use a 16 byte alignment for u/i128.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a fixme comment

f32_align: AbiAndPrefAlign::new(Align::from_bytes(4).unwrap()),
f64_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()),
pointer_size,
pointer_align: AbiAndPrefAlign::new(Align::from_bytes(pointer_size.bytes()).unwrap()),
aggregate_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()),
vector_align: vec![],
instruction_address_space: AddressSpace(0),
c_enum_min_size: Integer::I32,
})
}
196 changes: 196 additions & 0 deletions crates/hir-ty/src/layout/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use base_db::fixture::WithFixture;
use chalk_ir::{AdtId, TyKind};
use hir_def::{
db::DefDatabase,
layout::{Layout, LayoutError},
};

use crate::{test_db::TestDB, Interner, Substitution};

use super::layout_of_ty;

fn eval_goal(ra_fixture: &str) -> Result<Layout, LayoutError> {
let (db, file_id) = TestDB::with_single_file(ra_fixture);
let module_id = db.module_for_file(file_id);
let def_map = module_id.def_map(&db);
let scope = &def_map[module_id.local_id].scope;
let adt_id = scope
.declarations()
.into_iter()
.find_map(|x| match x {
hir_def::ModuleDefId::AdtId(x) => {
let name = match x {
hir_def::AdtId::StructId(x) => db.struct_data(x).name.to_string(),
hir_def::AdtId::UnionId(x) => db.union_data(x).name.to_string(),
hir_def::AdtId::EnumId(x) => db.enum_data(x).name.to_string(),
};
if name == "Goal" {
Some(x)
} else {
None
}
}
_ => None,
})
.unwrap();
let goal_ty = TyKind::Adt(AdtId(adt_id), Substitution::empty(Interner)).intern(Interner);
layout_of_ty(&db, &goal_ty)
}

fn check_size_and_align(ra_fixture: &str, size: u64, align: u64) {
let l = eval_goal(ra_fixture).unwrap();
assert_eq!(l.size.bytes(), size);
assert_eq!(l.align.abi.bytes(), align);
}

fn check_fail(ra_fixture: &str, e: LayoutError) {
let r = eval_goal(ra_fixture);
assert_eq!(r, Err(e));
}

macro_rules! size_and_align {
(minicore: $($x:tt),*;$($t:tt)*) => {
{
#[allow(dead_code)]
$($t)*
check_size_and_align(
&format!("//- minicore: {}\n{}", stringify!($($x),*), stringify!($($t)*)),
::std::mem::size_of::<Goal>() as u64,
::std::mem::align_of::<Goal>() as u64,
);
}
};
($($t:tt)*) => {
{
#[allow(dead_code)]
$($t)*
check_size_and_align(
stringify!($($t)*),
::std::mem::size_of::<Goal>() as u64,
::std::mem::align_of::<Goal>() as u64,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat trick! This should even work when cross-compiling r-a.

);
}
};
}

#[test]
fn hello_world() {
size_and_align! {
struct Goal(i32);
}
}

#[test]
fn field_order_optimization() {
size_and_align! {
struct Goal(u8, i32, u8);
}
size_and_align! {
#[repr(C)]
struct Goal(u8, i32, u8);
}
}

#[test]
fn recursive() {
size_and_align! {
struct Goal {
left: &'static Goal,
right: &'static Goal,
}
}
size_and_align! {
struct BoxLike<T: ?Sized>(*mut T);
struct Goal(BoxLike<Goal>);
}
check_fail(
r#"struct Goal(Goal);"#,
LayoutError::UserError("infinite sized recursive type".to_string()),
);
check_fail(
r#"
struct Foo<T>(Foo<T>);
struct Goal(Foo<i32>);
"#,
LayoutError::UserError("infinite sized recursive type".to_string()),
);
}

#[test]
fn generic() {
size_and_align! {
struct Pair<A, B>(A, B);
struct Goal(Pair<Pair<i32, u8>, i64>);
}
size_and_align! {
struct X<const N: usize> {
field1: [i32; N],
field2: [u8; N],
}
struct Goal(X<1000>);
}
}

#[test]
fn enums() {
size_and_align! {
enum Goal {
Quit,
Move { x: i32, y: i32 },
ChangeColor(i32, i32, i32),
}
}
}

#[test]
fn primitives() {
size_and_align! {
struct Goal(i32, i128, isize, usize, f32, f64, bool, char);
}
}

#[test]
fn tuple() {
size_and_align! {
struct Goal((), (i32, u64, bool));
}
}

#[test]
fn non_zero() {
size_and_align! {
minicore: non_zero, option;
use core::num::NonZeroU8;
struct Goal(Option<NonZeroU8>);
}
}

#[test]
fn niche_optimization() {
size_and_align! {
minicore: option;
struct Goal(Option<&'static i32>);
}
size_and_align! {
minicore: option;
struct Goal(Option<Option<bool>>);
}
}

#[test]
fn enums_with_discriminants() {
size_and_align! {
enum Goal {
A = 1000,
B = 2000,
C = 3000,
}
}
size_and_align! {
enum Goal {
A = 254,
B,
C, // implicitly becomes 256, so we need two bytes
}
}
}
2 changes: 2 additions & 0 deletions crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ pub mod display;
pub mod method_resolution;
pub mod primitive;
pub mod traits;
pub mod layout;
pub mod lang_items;

#[cfg(test)]
mod tests;
49 changes: 44 additions & 5 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -39,12 +39,13 @@ use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId, ProcMacroKind};
use either::Either;
use hir_def::{
adt::{ReprData, VariantData},
adt::VariantData,
body::{BodyDiagnostic, SyntheticSyntax},
expr::{BindingAnnotation, LabelId, Pat, PatId},
generics::{TypeOrConstParamData, TypeParamProvenance},
item_tree::ItemTreeNode,
lang_item::LangItemTarget,
layout::{Layout, LayoutError, ReprOptions},
nameres::{self, diagnostics::DefDiagnostic},
per_ns::PerNs,
resolver::{HasResolver, Resolver},
@@ -59,6 +60,7 @@ use hir_ty::{
all_super_traits, autoderef,
consteval::{unknown_const_as_generic, ComputedExpr, ConstEvalError, ConstExt},
diagnostics::BodyValidationDiagnostic,
layout::layout_of_ty,
method_resolution::{self, TyFingerprint},
primitive::UintTy,
traits::FnTrait,
@@ -844,6 +846,10 @@ impl Field {
self.parent.variant_data(db).fields()[self.id].name.clone()
}

pub fn index(&self) -> usize {
u32::from(self.id.into_raw()) as usize
}

/// Returns the type as in the signature of the struct (i.e., with
/// placeholder types for type parameters). Only use this in the context of
/// the field definition.
@@ -859,6 +865,10 @@ impl Field {
Type::new(db, var_id, ty)
}

pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
layout_of_ty(db, &self.ty(db).ty)
}

pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef {
self.parent
}
@@ -900,7 +910,7 @@ impl Struct {
Type::from_def(db, self.id)
}

pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprData> {
pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprOptions> {
db.struct_data(self.id).repr.clone()
}

@@ -984,8 +994,30 @@ impl Enum {
Type::new_for_crate(
self.id.lookup(db.upcast()).container.krate(),
TyBuilder::builtin(match db.enum_data(self.id).variant_body_type() {
Either::Left(builtin) => hir_def::builtin_type::BuiltinType::Int(builtin),
Either::Right(builtin) => hir_def::builtin_type::BuiltinType::Uint(builtin),
hir_def::layout::IntegerType::Pointer(sign) => match sign {
true => hir_def::builtin_type::BuiltinType::Int(
hir_def::builtin_type::BuiltinInt::Isize,
),
false => hir_def::builtin_type::BuiltinType::Uint(
hir_def::builtin_type::BuiltinUint::Usize,
),
},
hir_def::layout::IntegerType::Fixed(i, sign) => match sign {
true => hir_def::builtin_type::BuiltinType::Int(match i {
hir_def::layout::Integer::I8 => hir_def::builtin_type::BuiltinInt::I8,
hir_def::layout::Integer::I16 => hir_def::builtin_type::BuiltinInt::I16,
hir_def::layout::Integer::I32 => hir_def::builtin_type::BuiltinInt::I32,
hir_def::layout::Integer::I64 => hir_def::builtin_type::BuiltinInt::I64,
hir_def::layout::Integer::I128 => hir_def::builtin_type::BuiltinInt::I128,
}),
false => hir_def::builtin_type::BuiltinType::Uint(match i {
hir_def::layout::Integer::I8 => hir_def::builtin_type::BuiltinUint::U8,
hir_def::layout::Integer::I16 => hir_def::builtin_type::BuiltinUint::U16,
hir_def::layout::Integer::I32 => hir_def::builtin_type::BuiltinUint::U32,
hir_def::layout::Integer::I64 => hir_def::builtin_type::BuiltinUint::U64,
hir_def::layout::Integer::I128 => hir_def::builtin_type::BuiltinUint::U128,
}),
},
}),
)
}
@@ -1076,6 +1108,13 @@ impl Adt {
})
}

pub fn layout(self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
if db.generic_params(self.into()).iter().count() != 0 {
return Err(LayoutError::HasPlaceholder);
}
db.layout_of_adt(self.into(), Substitution::empty(Interner))
}

/// Turns this ADT into a type. Any type parameters of the ADT will be
/// turned into unknown types, which is good for e.g. finding the most
/// general set of completions, but will not look very nice when printed.
@@ -3031,7 +3070,7 @@ impl Type {

let adt = adt_id.into();
match adt {
Adt::Struct(s) => matches!(s.repr(db), Some(ReprData { packed: true, .. })),
Adt::Struct(s) => s.repr(db).unwrap_or_default().pack.is_some(),
_ => false,
}
}
47 changes: 44 additions & 3 deletions crates/ide/src/hover/render.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,9 @@
use std::fmt::Display;

use either::Either;
use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
use hir::{
Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo,
};
use ide_db::{
base_db::SourceDatabase,
defs::Definition,
@@ -388,10 +390,30 @@ pub(super) fn definition(
let mod_path = definition_mod_path(db, &def);
let (label, docs) = match def {
Definition::Macro(it) => label_and_docs(db, it),
Definition::Field(it) => label_and_docs(db, it),
Definition::Field(it) => label_and_layout_info_and_docs(db, it, |&it| {
let var_def = it.parent_def(db);
let id = it.index();
let layout = it.layout(db).ok()?;
let offset = match var_def {
hir::VariantDef::Struct(s) => {
let layout = Adt::from(s).layout(db).ok()?;
layout.fields.offset(id)
}
_ => return None,
};
Some(format!(
"size = {}, align = {}, offset = {}",
layout.size.bytes(),
layout.align.abi.bytes(),
offset.bytes()
))
}),
Definition::Module(it) => label_and_docs(db, it),
Definition::Function(it) => label_and_docs(db, it),
Definition::Adt(it) => label_and_docs(db, it),
Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| {
let layout = it.layout(db).ok()?;
Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes()))
}),
Definition::Variant(it) => label_value_and_docs(db, it, |&it| {
if !it.parent_enum(db).is_data_carrying(db) {
match it.eval(db) {
@@ -489,6 +511,25 @@ where
(label, docs)
}

fn label_and_layout_info_and_docs<D, E, V>(
db: &RootDatabase,
def: D,
value_extractor: E,
) -> (String, Option<hir::Documentation>)
where
D: HasAttrs + HirDisplay,
E: Fn(&D) -> Option<V>,
V: Display,
{
let label = if let Some(value) = value_extractor(&def) {
format!("{} // {}", def.display(db), value)
} else {
def.display(db).to_string()
};
let docs = def.attrs(db).docs();
(label, docs)
}

fn label_value_and_docs<D, E, V>(
db: &RootDatabase,
def: D,
219 changes: 120 additions & 99 deletions crates/ide/src/hover/tests.rs
Original file line number Diff line number Diff line change
@@ -522,6 +522,27 @@ fn main() { }
);
}

#[test]
fn hover_field_offset() {
// Hovering over the field when instantiating
check(
r#"
struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
"#,
expect![[r#"
*field_a*
```rust
test::Foo
```
```rust
field_a: u8 // size = 1, align = 1, offset = 4
```
"#]],
);
}

#[test]
fn hover_shows_struct_field_info() {
// Hovering over the field when instantiating
@@ -534,16 +555,16 @@ fn main() {
}
"#,
expect![[r#"
*field_a*
*field_a*
```rust
test::Foo
```
```rust
test::Foo
```
```rust
field_a: u32
```
"#]],
```rust
field_a: u32 // size = 4, align = 4, offset = 0
```
"#]],
);

// Hovering over the field in the definition
@@ -556,16 +577,16 @@ fn main() {
}
"#,
expect![[r#"
*field_a*
*field_a*
```rust
test::Foo
```
```rust
test::Foo
```
```rust
field_a: u32
```
"#]],
```rust
field_a: u32 // size = 4, align = 4, offset = 0
```
"#]],
);
}

@@ -1508,30 +1529,30 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r##"
*Bar*
expect![[r#"
*Bar*
```rust
test
```
```rust
test
```
```rust
struct Bar
```
```rust
struct Bar // size = 0, align = 1
```
---
---
This is an example
multiline doc
This is an example
multiline doc
# Example
# Example
```
let five = 5;
```
let five = 5;
assert_eq!(6, my_crate::add_one(5));
```
"##]],
assert_eq!(6, my_crate::add_one(5));
```
"#]],
);
}

@@ -1545,20 +1566,20 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
*Bar*
*Bar*
```rust
test
```
```rust
test
```
```rust
struct Bar
```
```rust
struct Bar // size = 0, align = 1
```
---
---
bar docs
"#]],
bar docs
"#]],
);
}

@@ -1574,22 +1595,22 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
*Bar*
*Bar*
```rust
test
```
```rust
test
```
```rust
struct Bar
```
```rust
struct Bar // size = 0, align = 1
```
---
---
bar docs 0
bar docs 1
bar docs 2
"#]],
bar docs 0
bar docs 1
bar docs 2
"#]],
);
}

@@ -1602,20 +1623,20 @@ pub struct Foo;
pub struct B$0ar
"#,
expect![[r#"
*Bar*
*Bar*
```rust
test
```
```rust
test
```
```rust
pub struct Bar
```
```rust
pub struct Bar // size = 0, align = 1
```
---
---
[external](https://www.google.com)
"#]],
[external](https://www.google.com)
"#]],
);
}

@@ -1629,20 +1650,20 @@ pub struct Foo;
pub struct B$0ar
"#,
expect![[r#"
*Bar*
*Bar*
```rust
test
```
```rust
test
```
```rust
pub struct Bar
```
```rust
pub struct Bar // size = 0, align = 1
```
---
---
[baz](Baz)
"#]],
[baz](Baz)
"#]],
);
}

@@ -2960,7 +2981,7 @@ fn main() {
```
```rust
f: i32
f: i32 // size = 4, align = 4, offset = 0
```
"#]],
);
@@ -4203,20 +4224,20 @@ pub fn gimme() -> theitem::TheItem {
}
"#,
expect![[r#"
*[`TheItem`]*
*[`TheItem`]*
```rust
test::theitem
```
```rust
test::theitem
```
```rust
pub struct TheItem
```
```rust
pub struct TheItem // size = 0, align = 1
```
---
---
This is the item. Cool!
"#]],
This is the item. Cool!
"#]],
);
}

@@ -4351,20 +4372,20 @@ mod string {
}
"#,
expect![[r#"
*String*
*String*
```rust
main
```
```rust
main
```
```rust
struct String
```
```rust
struct String // size = 0, align = 1
```
---
---
Custom `String` type.
"#]],
Custom `String` type.
"#]],
)
}

@@ -5025,7 +5046,7 @@ foo_macro!(
```
```rust
pub struct Foo
pub struct Foo // size = 0, align = 1
```
---
@@ -5040,7 +5061,7 @@ fn hover_intra_in_attr() {
check(
r#"
#[doc = "Doc comment for [`Foo$0`]"]
pub struct Foo;
pub struct Foo(i32);
"#,
expect![[r#"
*[`Foo`]*
@@ -5050,7 +5071,7 @@ pub struct Foo;
```
```rust
pub struct Foo
pub struct Foo // size = 4, align = 4
```
---
10 changes: 10 additions & 0 deletions crates/test-utils/src/minicore.rs
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
//! index: sized
//! iterator: option
//! iterators: iterator, fn
//! non_zero:
//! option:
//! ord: eq, option
//! pin:
@@ -680,6 +681,15 @@ mod macros {
}
// endregion:derive

// region:non_zero
pub mod num {
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start(1)]
#[rustc_nonnull_optimization_guaranteed]
pub struct NonZeroU8(u8);
}
// endregion:non_zero

// region:bool_impl
#[lang = "bool"]
impl bool {
8 changes: 8 additions & 0 deletions lib/la-arena/src/map.rs
Original file line number Diff line number Diff line change
@@ -86,6 +86,14 @@ impl<T, V> ArenaMap<Idx<T>, V> {
self.v.iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_ref()?)))
}

/// Returns an iterator over the arena indexes and values in the map.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Idx<T>, &mut V)> {
self.v
.iter_mut()
.enumerate()
.filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?)))
}

/// Gets the given key's corresponding entry in the map for in-place manipulation.
pub fn entry(&mut self, idx: Idx<T>) -> Entry<'_, Idx<T>, V> {
let idx = Self::to_idx(idx);