Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 63acf60

Browse files
Lay the foundation for diagnostics in ty lowering, and implement a first diagnostic
The diagnostic implemented is a simple one (E0109). It serves as a test for the new foundation. This commit only implements diagnostics for type in bodies and body-carrying signatures; the next commit will include diagnostics in the rest of the things. Also fix one weird bug that was detected when implementing this that caused `Fn::(A, B) -> C` (which is a valid, if bizarre, alternative syntax to `Fn(A, B) -> C` to lower incorrectly. And also fix a maybe-bug where parentheses were sneaked into a code string needlessly; this was not detected until now because the parentheses were removed (by the make-AST family API), but with a change in this commit they are now inserted. So fix that too.
1 parent 2a949d8 commit 63acf60

File tree

19 files changed

+811
-80
lines changed

19 files changed

+811
-80
lines changed

src/tools/rust-analyzer/Cargo.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ dependencies = [
509509
"base-db",
510510
"cfg",
511511
"either",
512+
"expect-test",
512513
"hir-def",
513514
"hir-expand",
514515
"hir-ty",
@@ -519,6 +520,9 @@ dependencies = [
519520
"span",
520521
"stdx",
521522
"syntax",
523+
"syntax-bridge",
524+
"test-fixture",
525+
"test-utils",
522526
"tracing",
523527
"triomphe",
524528
"tt",

src/tools/rust-analyzer/crates/hir-def/src/body.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ pub struct BodySourceMap {
141141
field_map_back: FxHashMap<ExprId, FieldSource>,
142142
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
143143

144-
types: TypesSourceMap,
144+
pub types: TypesSourceMap,
145145

146146
// FIXME: Make this a sane struct.
147147
template_map: Option<

src/tools/rust-analyzer/crates/hir-def/src/hir/type_ref.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ pub struct TypesSourceMap {
219219
}
220220

221221
impl TypesSourceMap {
222+
pub const EMPTY: Self = Self { types_map_back: ArenaMap::new() };
223+
222224
pub fn type_syntax(&self, id: TypeRefId) -> Result<TypeSource, SyntheticSyntax> {
223225
self.types_map_back.get(id).cloned().ok_or(SyntheticSyntax)
224226
}

src/tools/rust-analyzer/crates/hir-def/src/path.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`.
22
mod lower;
3+
#[cfg(test)]
4+
mod tests;
35

46
use std::{
57
fmt::{self, Display},
@@ -19,6 +21,8 @@ use syntax::ast;
1921

2022
pub use hir_expand::mod_path::{path, ModPath, PathKind};
2123

24+
pub use lower::hir_segment_to_ast_segment;
25+
2226
#[derive(Debug, Clone, PartialEq, Eq)]
2327
pub enum ImportAlias {
2428
/// Unnamed alias, as in `use Foo as _;`
@@ -230,7 +234,7 @@ impl Path {
230234
}
231235
}
232236

233-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
237+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
234238
pub struct PathSegment<'a> {
235239
pub name: &'a Name,
236240
pub args_and_bindings: Option<&'a GenericArgs>,
@@ -274,6 +278,12 @@ impl<'a> PathSegments<'a> {
274278
generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)),
275279
}
276280
}
281+
pub fn strip_last(&self) -> PathSegments<'a> {
282+
PathSegments {
283+
segments: self.segments.split_last().map_or(&[], |it| it.1),
284+
generic_args: self.generic_args.map(|it| it.split_last().map_or(&[][..], |it| it.1)),
285+
}
286+
}
277287
pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> {
278288
self.segments
279289
.iter()

src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,31 @@ use crate::{
1717
type_ref::{LifetimeRef, TypeBound, TypeRef},
1818
};
1919

20+
#[cfg(test)]
21+
thread_local! {
22+
/// This is used to test `hir_segment_to_ast_segment()`. It's a hack, but it makes testing much easier.
23+
pub(super) static SEGMENT_LOWERING_MAP: std::cell::RefCell<rustc_hash::FxHashMap<ast::PathSegment, usize>> = std::cell::RefCell::default();
24+
}
25+
2026
/// Converts an `ast::Path` to `Path`. Works with use trees.
2127
/// It correctly handles `$crate` based path from macro call.
28+
// If you modify the logic of the lowering, make sure to check if `hir_segment_to_ast_segment()`
29+
// also needs an update.
2230
pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<Path> {
2331
let mut kind = PathKind::Plain;
2432
let mut type_anchor = None;
2533
let mut segments = Vec::new();
2634
let mut generic_args = Vec::new();
35+
#[cfg(test)]
36+
let mut ast_segments = Vec::new();
37+
#[cfg(test)]
38+
let mut ast_segments_offset = 0;
39+
#[allow(unused_mut)]
40+
let mut push_segment = |_segment: &ast::PathSegment, segments: &mut Vec<Name>, name| {
41+
#[cfg(test)]
42+
ast_segments.push(_segment.clone());
43+
segments.push(name);
44+
};
2745
loop {
2846
let segment = path.segment()?;
2947

@@ -34,6 +52,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
3452
match segment.kind()? {
3553
ast::PathSegmentKind::Name(name_ref) => {
3654
if name_ref.text() == "$crate" {
55+
if path.qualifier().is_some() {
56+
// FIXME: Report an error.
57+
return None;
58+
}
3759
break kind = resolve_crate_root(
3860
ctx.db.upcast(),
3961
ctx.span_map().span_for_range(name_ref.syntax().text_range()).ctx,
@@ -56,10 +78,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
5678
generic_args.resize(segments.len(), None);
5779
generic_args.push(args);
5880
}
59-
segments.push(name);
81+
push_segment(&segment, &mut segments, name);
6082
}
6183
ast::PathSegmentKind::SelfTypeKw => {
62-
segments.push(Name::new_symbol_root(sym::Self_.clone()));
84+
push_segment(&segment, &mut segments, Name::new_symbol_root(sym::Self_.clone()));
6385
}
6486
ast::PathSegmentKind::Type { type_ref, trait_ref } => {
6587
assert!(path.qualifier().is_none()); // this can only occur at the first segment
@@ -81,6 +103,10 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
81103
kind = mod_path.kind;
82104

83105
segments.extend(mod_path.segments().iter().cloned().rev());
106+
#[cfg(test)]
107+
{
108+
ast_segments_offset = mod_path.segments().len();
109+
}
84110
if let Some(path_generic_args) = path_generic_args {
85111
generic_args.resize(segments.len() - num_segments, None);
86112
generic_args.extend(Vec::from(path_generic_args).into_iter().rev());
@@ -112,10 +138,18 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
112138
}
113139
}
114140
ast::PathSegmentKind::CrateKw => {
141+
if path.qualifier().is_some() {
142+
// FIXME: Report an error.
143+
return None;
144+
}
115145
kind = PathKind::Crate;
116146
break;
117147
}
118148
ast::PathSegmentKind::SelfKw => {
149+
if path.qualifier().is_some() {
150+
// FIXME: Report an error.
151+
return None;
152+
}
119153
// don't break out if `self` is the last segment of a path, this mean we got a
120154
// use tree like `foo::{self}` which we want to resolve as `foo`
121155
if !segments.is_empty() {
@@ -162,6 +196,13 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
162196
}
163197
}
164198

199+
#[cfg(test)]
200+
{
201+
ast_segments.reverse();
202+
SEGMENT_LOWERING_MAP
203+
.with_borrow_mut(|map| map.extend(ast_segments.into_iter().zip(ast_segments_offset..)));
204+
}
205+
165206
let mod_path = Interned::new(ModPath::from_segments(kind, segments));
166207
if type_anchor.is_none() && generic_args.is_empty() {
167208
return Some(Path::BarePath(mod_path));
@@ -181,6 +222,41 @@ pub(super) fn lower_path(ctx: &mut LowerCtx<'_>, mut path: ast::Path) -> Option<
181222
}
182223
}
183224

225+
/// This function finds the AST segment that corresponds to the HIR segment
226+
/// with index `segment_idx` on the path that is lowered from `path`.
227+
pub fn hir_segment_to_ast_segment(path: &ast::Path, segment_idx: u32) -> Option<ast::PathSegment> {
228+
// Too tightly coupled to `lower_path()`, but unfortunately we cannot decouple them,
229+
// as keeping source maps for all paths segments will have a severe impact on memory usage.
230+
231+
let mut segments = path.segments();
232+
if let Some(ast::PathSegmentKind::Type { trait_ref: Some(trait_ref), .. }) =
233+
segments.clone().next().and_then(|it| it.kind())
234+
{
235+
segments.next();
236+
return find_segment(trait_ref.path()?.segments().chain(segments), segment_idx);
237+
}
238+
return find_segment(segments, segment_idx);
239+
240+
fn find_segment(
241+
segments: impl Iterator<Item = ast::PathSegment>,
242+
segment_idx: u32,
243+
) -> Option<ast::PathSegment> {
244+
segments
245+
.filter(|segment| match segment.kind() {
246+
Some(
247+
ast::PathSegmentKind::CrateKw
248+
| ast::PathSegmentKind::SelfKw
249+
| ast::PathSegmentKind::SuperKw
250+
| ast::PathSegmentKind::Type { .. },
251+
)
252+
| None => false,
253+
Some(ast::PathSegmentKind::Name(name)) => name.text() != "$crate",
254+
Some(ast::PathSegmentKind::SelfTypeKw) => true,
255+
})
256+
.nth(segment_idx as usize)
257+
}
258+
}
259+
184260
pub(super) fn lower_generic_args(
185261
lower_ctx: &mut LowerCtx<'_>,
186262
node: ast::GenericArgList,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use expect_test::{expect, Expect};
2+
use span::Edition;
3+
use syntax::ast::{self, make};
4+
use test_fixture::WithFixture;
5+
6+
use crate::{
7+
lower::LowerCtx,
8+
path::{
9+
lower::{hir_segment_to_ast_segment, SEGMENT_LOWERING_MAP},
10+
Path,
11+
},
12+
pretty,
13+
test_db::TestDB,
14+
type_ref::{TypesMap, TypesSourceMap},
15+
};
16+
17+
fn lower_path(path: ast::Path) -> (TestDB, TypesMap, Option<Path>) {
18+
let (db, file_id) = TestDB::with_single_file("");
19+
let mut types_map = TypesMap::default();
20+
let mut types_source_map = TypesSourceMap::default();
21+
let mut ctx = LowerCtx::new(&db, file_id.into(), &mut types_map, &mut types_source_map);
22+
let lowered_path = ctx.lower_path(path);
23+
(db, types_map, lowered_path)
24+
}
25+
26+
#[track_caller]
27+
fn check_hir_to_ast(path: &str, ignore_segments: &[&str]) {
28+
let path = make::path_from_text(path);
29+
SEGMENT_LOWERING_MAP.with_borrow_mut(|map| map.clear());
30+
let _ = lower_path(path.clone()).2.expect("failed to lower path");
31+
SEGMENT_LOWERING_MAP.with_borrow(|map| {
32+
for (segment, segment_idx) in map {
33+
if ignore_segments.contains(&&*segment.to_string()) {
34+
continue;
35+
}
36+
37+
let restored_segment = hir_segment_to_ast_segment(&path, *segment_idx as u32)
38+
.unwrap_or_else(|| {
39+
panic!(
40+
"failed to map back segment `{segment}` \
41+
numbered {segment_idx} in HIR from path `{path}`"
42+
)
43+
});
44+
assert_eq!(
45+
segment, &restored_segment,
46+
"mapping back `{segment}` numbered {segment_idx} in HIR \
47+
from path `{path}` produced incorrect segment `{restored_segment}`"
48+
);
49+
}
50+
});
51+
}
52+
53+
#[test]
54+
fn hir_to_ast_trait_ref() {
55+
check_hir_to_ast("<A as B::C::D>::E::F", &["A"]);
56+
}
57+
58+
#[test]
59+
fn hir_to_ast_plain_path() {
60+
check_hir_to_ast("A::B::C::D::E::F", &[]);
61+
}
62+
63+
#[test]
64+
fn hir_to_ast_crate_path() {
65+
check_hir_to_ast("crate::A::B::C", &[]);
66+
check_hir_to_ast("crate::super::super::A::B::C", &[]);
67+
}
68+
69+
#[test]
70+
fn hir_to_ast_self_path() {
71+
check_hir_to_ast("self::A::B::C", &[]);
72+
check_hir_to_ast("self::super::super::A::B::C", &[]);
73+
}
74+
75+
#[test]
76+
fn hir_to_ast_super_path() {
77+
check_hir_to_ast("super::A::B::C", &[]);
78+
check_hir_to_ast("super::super::super::A::B::C", &[]);
79+
}
80+
81+
#[test]
82+
fn hir_to_ast_type_anchor_path() {
83+
check_hir_to_ast("<A::B>::C::D", &["A", "B"]);
84+
}
85+
86+
#[test]
87+
fn hir_to_ast_path_super_in_middle() {
88+
check_hir_to_ast("A::super::B::super::super::C::D", &[]);
89+
}
90+
91+
#[track_caller]
92+
fn check_fail_lowering(path: &str) {
93+
let (_, _, lowered_path) = lower_path(make::path_from_text(path));
94+
assert!(lowered_path.is_none(), "path `{path}` should fail lowering");
95+
}
96+
97+
#[test]
98+
fn keywords_in_middle_fail_lowering1() {
99+
check_fail_lowering("self::A::self::B::super::C::crate::D");
100+
}
101+
102+
#[test]
103+
fn keywords_in_middle_fail_lowering2() {
104+
check_fail_lowering("A::super::self::C::D");
105+
}
106+
107+
#[test]
108+
fn keywords_in_middle_fail_lowering3() {
109+
check_fail_lowering("A::crate::B::C::D");
110+
}
111+
112+
#[track_caller]
113+
fn check_path_lowering(path: &str, expected: Expect) {
114+
let (db, types_map, lowered_path) = lower_path(make::path_from_text(path));
115+
let lowered_path = lowered_path.expect("failed to lower path");
116+
let mut buf = String::new();
117+
pretty::print_path(&db, &lowered_path, &types_map, &mut buf, Edition::CURRENT)
118+
.expect("failed to pretty-print path");
119+
expected.assert_eq(&buf);
120+
}
121+
122+
#[test]
123+
fn fn_like_path_with_coloncolon() {
124+
check_path_lowering("Fn::(A, B) -> C", expect![[r#"Fn::<(A, B), Output = C>"#]]);
125+
check_path_lowering("Fn::(A, B)", expect![[r#"Fn::<(A, B), Output = ()>"#]]);
126+
}

src/tools/rust-analyzer/crates/hir-expand/src/files.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ impl<FileId: FileIdToSyntax, T> InFileWrapper<FileId, T> {
180180
}
181181
}
182182

183+
#[allow(private_bounds)]
184+
impl<FileId: FileIdToSyntax, N: AstNode> InFileWrapper<FileId, AstPtr<N>> {
185+
pub fn to_node(&self, db: &dyn ExpandDatabase) -> N {
186+
self.value.to_node(&self.file_syntax(db))
187+
}
188+
}
189+
183190
impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
184191
pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> {
185192
self.with_value(self.value.syntax())

0 commit comments

Comments
 (0)