Implement semitransparent hygiene

Or macro_rules hygiene, or mixed site hygiene. In other words, hygiene for variables and labels but not items.

The realization that made me implement this was that while "full" hygiene (aka. def site hygiene) is really hard for us to implement, and will likely involve intrusive changes and performance losses, since every `Name` will have to carry hygiene, mixed site hygiene is very local: it applies only to bodies, and we very well can save it in a side map with minor losses.

This fixes one diagnostic in r-a that was about `izip!()` using hygiene (yay!) but it introduces a huge number of others, because of #18262. Up until now this issue wasn't a major problem because it only affected few cases, but with hygiene identifiers referred by macros like that are not resolved at all. The next commit will fix that.
This commit is contained in:
Chayim Refael Friedman 2024-10-07 23:02:02 +03:00
parent c286786888
commit 8adcbdcc49
23 changed files with 394 additions and 124 deletions

View file

@ -3,7 +3,7 @@
use base_db::{ra_salsa::Cycle, CrateId};
use chalk_ir::{cast::Cast, BoundVar, DebruijnIndex};
use hir_def::{
body::Body,
body::{Body, HygieneId},
hir::{Expr, ExprId},
path::Path,
resolver::{Resolver, ValueNs},
@ -80,7 +80,7 @@ pub(crate) fn path_to_const<'g>(
debruijn: DebruijnIndex,
expected_ty: Ty,
) -> Option<Const> {
match resolver.resolve_path_in_value_ns_fully(db.upcast(), path) {
match resolver.resolve_path_in_value_ns_fully(db.upcast(), path, HygieneId::ROOT) {
Some(ValueNs::GenericParam(p)) => {
let ty = db.const_param_ty(p);
let value = match mode {

View file

@ -289,10 +289,12 @@ impl ExprValidator {
match &self.body[scrutinee_expr] {
Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
Expr::Path(path) => {
let value_or_partial = self
.owner
.resolver(db.upcast())
.resolve_path_in_value_ns_fully(db.upcast(), path);
let value_or_partial =
self.owner.resolver(db.upcast()).resolve_path_in_value_ns_fully(
db.upcast(),
path,
self.body.expr_path_hygiene(scrutinee_expr),
);
value_or_partial.map_or(true, |v| !matches!(v, ValueNs::StaticId(_)))
}
Expr::Field { expr, .. } => match self.infer.type_of_expr[*expr].kind(Interner) {

View file

@ -77,7 +77,8 @@ fn walk_unsafe(
) {
let mut mark_unsafe_path = |path, node| {
let g = resolver.update_to_inner_scope(db.upcast(), def, current);
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path);
let hygiene = body.expr_or_pat_path_hygiene(node);
let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path, hygiene);
if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
let static_data = db.static_data(id);
if static_data.mutable || (static_data.is_extern && !static_data.has_safe_kw) {

View file

@ -33,7 +33,7 @@ use chalk_ir::{
};
use either::Either;
use hir_def::{
body::Body,
body::{Body, HygieneId},
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
data::{ConstData, StaticData},
hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId},
@ -1398,7 +1398,7 @@ impl<'a> InferenceContext<'a> {
};
let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into());
let (resolution, unresolved) = if value_ns {
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path) {
match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, HygieneId::ROOT) {
Some(ResolveValueResult::ValueNs(value, _)) => match value {
ValueNs::EnumVariantId(var) => {
let substs = ctx.substs_from_path(path, var.into(), true);

View file

@ -514,8 +514,11 @@ impl InferenceContext<'_> {
if path.type_anchor().is_some() {
return None;
}
let result = self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), path).and_then(
|result| match result {
let hygiene = self.body.expr_or_pat_path_hygiene(id);
let result = self
.resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), path, hygiene)
.and_then(|result| match result {
ValueNs::LocalBinding(binding) => {
let mir_span = match id {
ExprOrPatId::ExprId(id) => MirSpan::ExprId(id),
@ -525,8 +528,7 @@ impl InferenceContext<'_> {
Some(HirPlace { local: binding, projections: Vec::new() })
}
_ => None,
},
);
});
result
}

View file

@ -201,7 +201,11 @@ impl InferenceContext<'_> {
Expr::Path(Path::Normal { type_anchor: Some(_), .. }) => false,
Expr::Path(path) => self
.resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), path)
.resolve_path_in_value_ns_fully(
self.db.upcast(),
path,
self.body.expr_path_hygiene(expr),
)
.map_or(true, |res| matches!(res, ValueNs::LocalBinding(_) | ValueNs::StaticId(_))),
Expr::Underscore => true,
Expr::UnaryOp { op: UnaryOp::Deref, .. } => true,

View file

@ -164,9 +164,10 @@ impl InferenceContext<'_> {
let ty = self.table.normalize_associated_types_in(ty);
self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))?
} else {
let hygiene = self.body.expr_or_pat_path_hygiene(id);
// FIXME: report error, unresolved first path segment
let value_or_partial =
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?;
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene)?;
match value_or_partial {
ResolveValueResult::ValueNs(it, _) => (it, None),

View file

@ -6,6 +6,7 @@ use base_db::CrateId;
use chalk_ir::{cast::Cast, Mutability};
use either::Either;
use hir_def::{
body::HygieneId,
builtin_type::BuiltinType,
data::adt::{StructFlags, VariantData},
lang_item::LangItem,
@ -2953,6 +2954,7 @@ pub fn render_const_using_debug_impl(
let Some(ValueNs::FunctionId(format_fn)) = resolver.resolve_path_in_value_ns_fully(
db.upcast(),
&hir_def::path::Path::from_known_path_with_no_generic(path![std::fmt::format]),
HygieneId::ROOT,
) else {
not_supported!("std::fmt::format not found");
};

View file

@ -5,7 +5,7 @@ use std::{fmt::Write, iter, mem};
use base_db::ra_salsa::Cycle;
use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind};
use hir_def::{
body::Body,
body::{Body, HygieneId},
data::adt::{StructKind, VariantData},
hir::{
ArithOp, Array, BinaryOp, BindingAnnotation, BindingId, ExprId, LabelId, Literal,
@ -446,9 +446,10 @@ impl<'ctx> MirLowerCtx<'ctx> {
} else {
let resolver_guard =
self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id);
let hygiene = self.body.expr_path_hygiene(expr_id);
let result = self
.resolver
.resolve_path_in_value_ns_fully(self.db.upcast(), p)
.resolve_path_in_value_ns_fully(self.db.upcast(), p, hygiene)
.ok_or_else(|| {
MirLowerError::unresolved_path(self.db, p, self.edition())
})?;
@ -1361,7 +1362,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
|| MirLowerError::unresolved_path(self.db, c.as_ref(), edition);
let pr = self
.resolver
.resolve_path_in_value_ns(self.db.upcast(), c.as_ref())
.resolve_path_in_value_ns(self.db.upcast(), c.as_ref(), HygieneId::ROOT)
.ok_or_else(unresolved_name)?;
match pr {
ResolveValueResult::ValueNs(v, _) => {

View file

@ -137,7 +137,9 @@ impl MirLowerCtx<'_> {
Expr::Path(p) => {
let resolver_guard =
self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr_id);
let resolved = self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), p);
let hygiene = self.body.expr_path_hygiene(expr_id);
let resolved =
self.resolver.resolve_path_in_value_ns_fully(self.db.upcast(), p, hygiene);
self.resolver.reset_to_guard(resolver_guard);
let Some(pr) = resolved else {
return try_rvalue(self);

View file

@ -351,9 +351,10 @@ impl MirLowerCtx<'_> {
None => {
let unresolved_name =
|| MirLowerError::unresolved_path(self.db, p, self.edition());
let hygiene = self.body.pat_path_hygiene(pattern);
let pr = self
.resolver
.resolve_path_in_value_ns(self.db.upcast(), p)
.resolve_path_in_value_ns(self.db.upcast(), p, hygiene)
.ok_or_else(unresolved_name)?;
if let (

View file

@ -3720,3 +3720,20 @@ fn test() -> bool {
"#]],
);
}
#[test]
fn macro_semitransparent_hygiene() {
check_types(
r#"
macro_rules! m {
() => { let bar: i32; };
}
fn foo() {
let bar: bool;
m!();
bar;
// ^^^ bool
}
"#,
);
}