Unnamed repository; edit this file 'description' to name the repository.
Lower half-open, open and exclusive ranges in pattern types correctly
Chayim Refael Friedman 3 weeks ago
parent 135e874 · commit d1253ff
-rw-r--r--crates/hir-def/src/expr_store/lower.rs93
-rw-r--r--crates/hir-def/src/expr_store/pretty.rs1
-rw-r--r--crates/hir-def/src/lang_item.rs14
-rw-r--r--crates/hir-def/src/resolver.rs7
-rw-r--r--crates/hir-ty/src/consteval.rs10
-rw-r--r--crates/hir-ty/src/infer/path.rs25
-rw-r--r--crates/intern/src/symbol/symbols.rs3
-rw-r--r--crates/test-utils/src/minicore.rs48
8 files changed, 176 insertions, 25 deletions
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index 76503ac97c..33161c503e 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -35,8 +35,8 @@ use thin_vec::ThinVec;
use tt::TextRange;
use crate::{
- AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, ItemContainerId,
- MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
+ AdtId, BlockId, BlockLoc, ConstId, DefWithBodyId, FunctionId, GenericDefId, ImplId,
+ ItemContainerId, MacroId, ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
attrs::AttrFlags,
db::DefDatabase,
expr_store::{
@@ -721,7 +721,7 @@ impl<'db> ExprCollector<'db> {
),
ast::Type::PatternType(inner) => TypeRef::PatternType(
self.lower_type_ref_opt(inner.ty(), impl_trait_lower_fn),
- self.collect_pat_top(inner.pat()),
+ self.collect_ty_pat_opt(inner.pat()),
),
ast::Type::MacroType(mt) => match mt.macro_call() {
Some(mcall) => {
@@ -2912,6 +2912,93 @@ impl<'db> ExprCollector<'db> {
}
}
+ fn collect_ty_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
+ match pat {
+ Some(pat) => self.collect_ty_pat(pat),
+ None => self.missing_pat(),
+ }
+ }
+
+ fn collect_ty_pat(&mut self, pat: ast::Pat) -> PatId {
+ let ptr = AstPtr::new(&pat);
+ match pat {
+ ast::Pat::NotNull(_) => self.alloc_pat(Pat::NotNull, ptr),
+ ast::Pat::OrPat(pat) => {
+ let pat = pat.pats().map(|pat| self.collect_ty_pat(pat)).collect();
+ self.alloc_pat(Pat::Or(pat), ptr)
+ }
+ ast::Pat::RangePat(range_pat) => {
+ let start = range_pat
+ .start()
+ .map(|pat| {
+ self.with_fresh_binding_expr_root(|this| this.lower_ty_pat_range_side(pat))
+ })
+ .unwrap_or_else(|| self.lower_ty_pat_range_end(self.lang_items().RangeMin));
+ let end = range_pat
+ .end()
+ .map(|pat| match range_pat.op_kind() {
+ Some(ast::RangeOp::Inclusive) | None => self
+ .with_fresh_binding_expr_root(|this| this.lower_ty_pat_range_side(pat)),
+ Some(ast::RangeOp::Exclusive) => self.lower_excluded_range_end(pat),
+ })
+ .unwrap_or_else(|| self.lower_ty_pat_range_end(self.lang_items().RangeMax));
+ self.alloc_pat(
+ Pat::Range {
+ start: Some(start),
+ end: Some(end),
+ range_type: ast::RangeOp::Inclusive,
+ },
+ ptr,
+ )
+ }
+ ast::Pat::MacroPat(pat) => {
+ let Some(call) = pat.macro_call() else { return self.missing_pat() };
+ let ptr = AstPtr::new(&call);
+ self.collect_macro_call(call, ptr, true, |this, pat| this.collect_ty_pat_opt(pat))
+ }
+ _ => {
+ // FIXME: Emit an error.
+ self.alloc_pat(Pat::Missing, ptr)
+ }
+ }
+ }
+
+ fn lower_ty_pat_range_side(&mut self, pat: ast::Pat) -> ExprId {
+ match &pat {
+ ast::Pat::LiteralPat(it) => {
+ let Some((literal, _)) = pat_literal_to_hir(it) else { return self.missing_expr() };
+ self.alloc_expr_from_pat(Expr::Literal(literal), AstPtr::new(&pat))
+ }
+ _ => self.missing_expr(),
+ }
+ }
+
+ /// When a range has no end specified (`1..` or `1..=`) or no start specified (`..5` or `..=5`),
+ /// we instead use a constant of the MAX/MIN of the type.
+ /// This way the type system does not have to handle the lack of a start/end.
+ fn lower_ty_pat_range_end(&mut self, lang_item: Option<ConstId>) -> ExprId {
+ self.with_fresh_binding_expr_root(|this| {
+ this.alloc_expr_desugared(
+ this.lang_path(lang_item).map(Expr::Path).unwrap_or(Expr::Missing),
+ )
+ })
+ }
+
+ /// Lowers the range end of an exclusive range (`2..5`) to an inclusive range 2..=(5 - 1).
+ /// This way the type system doesn't have to handle the distinction between inclusive/exclusive ranges.
+ fn lower_excluded_range_end(&mut self, pat: ast::Pat) -> ExprId {
+ self.with_fresh_binding_expr_root(|this| {
+ let excluded_end = this.lower_ty_pat_range_side(pat);
+ let range_sub_path =
+ this.lang_path(this.lang_items().RangeSub).map(Expr::Path).unwrap_or(Expr::Missing);
+ let range_sub_path = this.alloc_expr_desugared(range_sub_path);
+ this.alloc_expr_desugared(Expr::Call {
+ callee: range_sub_path,
+ args: Box::new([excluded_end]),
+ })
+ })
+ }
+
// endregion: patterns
/// Returns `false` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `true` when
diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs
index 4dc7ebebbb..35e3fc44c3 100644
--- a/crates/hir-def/src/expr_store/pretty.rs
+++ b/crates/hir-def/src/expr_store/pretty.rs
@@ -1135,6 +1135,7 @@ impl Printer<'_> {
LangItemTarget::TypeAliasId(it) => write_name!(it),
LangItemTarget::TraitId(it) => write_name!(it),
LangItemTarget::EnumVariantId(it) => write_name!(it),
+ LangItemTarget::ConstId(it) => write_name!(it),
}
if let Some(s) = s {
diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs
index 483c3f16c6..f9d5a843bd 100644
--- a/crates/hir-def/src/lang_item.rs
+++ b/crates/hir-def/src/lang_item.rs
@@ -7,7 +7,7 @@ use intern::{Symbol, sym};
use stdx::impl_from;
use crate::{
- AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId,
+ AdtId, AssocItemId, AttrDefId, ConstId, Crate, EnumId, EnumVariantId, FunctionId, ImplId,
ItemContainerId, MacroId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
attrs::AttrFlags,
db::DefDatabase,
@@ -25,10 +25,11 @@ pub enum LangItemTarget {
TypeAliasId(TypeAliasId),
TraitId(TraitId),
EnumVariantId(EnumVariantId),
+ ConstId(ConstId),
}
impl_from!(
- EnumId, FunctionId, ImplId, StaticId, StructId, UnionId, TypeAliasId, TraitId, EnumVariantId for LangItemTarget
+ EnumId, FunctionId, ImplId, StaticId, StructId, UnionId, TypeAliasId, TraitId, EnumVariantId, ConstId for LangItemTarget
);
/// Salsa query. This will look for lang items in a specific crate.
@@ -51,7 +52,7 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option<Box<LangIt
match assoc {
AssocItemId::FunctionId(f) => lang_items.collect_lang_item(db, f),
AssocItemId::TypeAliasId(t) => lang_items.collect_lang_item(db, t),
- AssocItemId::ConstId(_) => (),
+ AssocItemId::ConstId(c) => lang_items.collect_lang_item(db, c),
}
}
}
@@ -68,7 +69,7 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option<Box<LangIt
AssocItemId::TypeAliasId(alias) => {
lang_items.collect_lang_item(db, alias)
}
- AssocItemId::ConstId(_) => {}
+ AssocItemId::ConstId(c) => lang_items.collect_lang_item(db, c),
}
});
}
@@ -93,6 +94,7 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option<Box<LangIt
ModuleDefId::TypeAliasId(t) => {
lang_items.collect_lang_item(db, t);
}
+ ModuleDefId::ConstId(c) => lang_items.collect_lang_item(db, c),
_ => {}
}
}
@@ -646,6 +648,10 @@ language_item_table! { LangItems =>
FieldBase, sym::field_base, TypeAliasId;
FieldType, sym::field_type, TypeAliasId;
+ RangeMin, sym::RangeMin, ConstId;
+ RangeMax, sym::RangeMax, ConstId;
+ RangeSub, sym::RangeSub, FunctionId;
+
@non_lang_core_traits:
core::default, Default;
core::fmt, Debug;
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 8320221ffc..0062e6c170 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -194,7 +194,8 @@ impl<'db> Resolver<'db> {
LangItemTarget::TraitId(it) => TypeNs::TraitId(it),
LangItemTarget::FunctionId(_)
| LangItemTarget::ImplId(_)
- | LangItemTarget::StaticId(_) => return None,
+ | LangItemTarget::StaticId(_)
+ | LangItemTarget::ConstId(_) => return None,
};
return Some((
type_ns,
@@ -337,6 +338,7 @@ impl<'db> Resolver<'db> {
LangItemTarget::StaticId(it) => ValueNs::StaticId(it),
LangItemTarget::StructId(it) => ValueNs::StructId(it),
LangItemTarget::EnumVariantId(it) => ValueNs::EnumVariantId(it),
+ LangItemTarget::ConstId(it) => ValueNs::ConstId(it),
LangItemTarget::UnionId(_)
| LangItemTarget::ImplId(_)
| LangItemTarget::TypeAliasId(_)
@@ -356,7 +358,8 @@ impl<'db> Resolver<'db> {
LangItemTarget::TraitId(it) => TypeNs::TraitId(it),
LangItemTarget::FunctionId(_)
| LangItemTarget::ImplId(_)
- | LangItemTarget::StaticId(_) => return None,
+ | LangItemTarget::StaticId(_)
+ | LangItemTarget::ConstId(_) => return None,
};
// Remaining segments start from 0 because lang paths have no segments other than the remaining.
return Some((
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs
index 9a6e2a87f2..fb52813c52 100644
--- a/crates/hir-ty/src/consteval.rs
+++ b/crates/hir-ty/src/consteval.rs
@@ -303,6 +303,7 @@ pub(crate) enum CreateConstError<'db> {
UsedForbiddenParam,
ResolveToNonConst,
DoesNotResolve,
+ ConstHasGenerics,
UnderscoreExpr,
TypeMismatch {
#[expect(unused, reason = "will need this for diagnostics")]
@@ -321,9 +322,11 @@ pub(crate) fn path_to_const<'a, 'db>(
let resolution = resolver
.resolve_path_in_value_ns_fully(db, path, HygieneId::ROOT)
.ok_or(CreateConstError::DoesNotResolve)?;
+ let no_generics = |def| crate::generics::generics(db, def).has_no_params();
let konst = match resolution {
- ValueNs::ConstId(id) => GeneralConstId::ConstId(id),
+ ValueNs::ConstId(id) if no_generics(id.into()) => GeneralConstId::ConstId(id),
ValueNs::StaticId(id) => GeneralConstId::StaticId(id),
+ ValueNs::ConstId(_) => return Err(CreateConstError::ConstHasGenerics),
ValueNs::GenericParam(param) => {
let index = generics().type_or_const_param_idx(param.into());
if forbid_params_after.is_some_and(|forbid_after| index >= forbid_after) {
@@ -363,7 +366,10 @@ pub(crate) fn create_anon_const<'a, 'db>(
Expr::Path(path)
if let konst =
path_to_const(interner.db, resolver, generics, forbid_params_after, path)
- && !matches!(konst, Err(CreateConstError::DoesNotResolve)) =>
+ && !matches!(
+ konst,
+ Err(CreateConstError::DoesNotResolve | CreateConstError::ConstHasGenerics)
+ ) =>
{
konst
}
diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs
index 704f15cc86..1c3d93ae6e 100644
--- a/crates/hir-ty/src/infer/path.rs
+++ b/crates/hir-ty/src/infer/path.rs
@@ -183,7 +183,30 @@ impl<'db> InferenceContext<'_, 'db> {
match value_or_partial {
ResolveValueResult::ValueNs(it) => {
drop_ctx(ctx, no_diagnostics);
- (it, None)
+
+ let args = if let Path::LangItem(..) = path {
+ let def_and_container = match it {
+ ValueNs::ConstId(it) => Some((it.into(), it.loc(self.db).container)),
+ ValueNs::FunctionId(it) => Some((it.into(), it.loc(self.db).container)),
+ _ => None,
+ };
+ let def_and_container =
+ def_and_container.and_then(|(def, container)| match container {
+ ItemContainerId::ImplId(it) => Some((def, it.into())),
+ ItemContainerId::TraitId(it) => Some((def, it.into())),
+ ItemContainerId::ExternBlockId(_)
+ | ItemContainerId::ModuleId(_) => None,
+ });
+ def_and_container.map(|(def, container)| {
+ let args = self.infcx().fresh_args_for_item(id.into(), container);
+ self.write_assoc_resolution(id, def, args);
+ args
+ })
+ } else {
+ None
+ };
+
+ (it, args)
}
ResolveValueResult::Partial(def, remaining_index) => {
// there may be more intermediate segments between the resolved one and
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index 6ad8006d18..f9ba0582ab 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -671,4 +671,7 @@ define_symbols! {
deref_patterns,
mut_ref,
type_changing_struct_update,
+ RangeMin,
+ RangeMax,
+ RangeSub,
}
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 190c59170f..bd15dca609 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -54,12 +54,12 @@
//! iterators: iterator, fn
//! manually_drop: drop
//! matches:
-//! non_null:
-//! non_zero: transmute, option
+//! non_null: pat
+//! non_zero: pat, transmute, option
//! option: panic
//! ord: eq, option
//! panic: fmt
-//! pat:
+//! pat: panic
//! phantom_data:
//! pin:
//! pointee: copy, send, sync, ord, hash, unpin, phantom_data
@@ -539,10 +539,10 @@ pub mod ptr {
// endregion:pointee
// region:non_null
- #[rustc_layout_scalar_valid_range_start(1)]
#[rustc_nonnull_optimization_guaranteed]
+ #[repr(transparent)]
pub struct NonNull<T: crate::marker::PointeeSized> {
- pointer: *const T,
+ pointer: crate::pattern_type!(*const T is !null),
}
// region:coerce_unsized
impl<T: crate::marker::PointeeSized, U: crate::marker::PointeeSized>
@@ -2327,28 +2327,50 @@ pub mod pat {
}
pub const trait RangePattern {
- /// Trait version of the inherent `MIN` assoc const.
#[lang = "RangeMin"]
const MIN: Self;
- /// Trait version of the inherent `MIN` assoc const.
#[lang = "RangeMax"]
const MAX: Self;
- /// A compile-time helper to subtract 1 for exclusive ranges.
#[lang = "RangeSub"]
- #[track_caller]
fn sub_one(self) -> Self;
}
+
+ impl const RangePattern for u8 {
+ const MIN: u8 = 0;
+ const MAX: u8 = 0xFF;
+ fn sub_one(self) -> Self {
+ if self == Self::MIN {
+ panic!("exclusive range end at minimum value of type")
+ } else {
+ self - 1
+ }
+ }
+ }
+
+ // region:coerce_unsized
+ impl<T: crate::marker::PointeeSized, U: crate::marker::PointeeSized>
+ crate::ops::CoerceUnsized<pattern_type!(*const U is !null)> for pattern_type!(*const T is !null)
+ where
+ T: crate::marker::Unsize<U>,
+ {
+ }
+ // endregion:coerce_unsized
+
+ // region:dispatch_from_dyn
+ impl<T: crate::ops::DispatchFromDyn<U>, U>
+ crate::ops::DispatchFromDyn<pattern_type!(U is !null)> for pattern_type!(T is !null)
+ {
+ }
+ // endregion:dispatch_from_dyn
}
// endregion:pat
// region:non_zero
pub mod num {
#[repr(transparent)]
- #[rustc_layout_scalar_valid_range_start(1)]
- #[rustc_nonnull_optimization_guaranteed]
- pub struct NonZeroU8(u8);
+ pub struct NonZeroU8(crate::pattern_type!(u8 is 1..));
}
// endregion:non_zero
@@ -2457,6 +2479,7 @@ pub mod prelude {
hash::derive::Hash, // :hash, derive
iter::{FromIterator, IntoIterator, Iterator}, // :iterator
macros::builtin::{derive, derive_const}, // :derive
+ macros::deref, // :deref_pat
marker::Copy, // :copy
marker::Send, // :send
marker::Sized, // :sized
@@ -2470,7 +2493,6 @@ pub mod prelude {
panic, // :panic
result::Result::{self, Err, Ok}, // :result
str::FromStr, // :str
- macros::deref, // :deref_pat
};
}