mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +00:00
Merge commit '21b06c1beb9bb59369ffd652f5d617bcf6952e05' into sync-from-ra
This commit is contained in:
parent
cac74d98f6
commit
e37cf75791
59 changed files with 1080 additions and 477 deletions
9
.github/workflows/metrics.yaml
vendored
9
.github/workflows/metrics.yaml
vendored
|
@ -67,7 +67,7 @@ jobs:
|
|||
other_metrics:
|
||||
strategy:
|
||||
matrix:
|
||||
names: [self, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18]
|
||||
names: [self, rustc_tests, ripgrep-13.0.0, webrender-2022, diesel-1.4.8, hyper-0.14.18]
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup_cargo, build_metrics]
|
||||
|
||||
|
@ -118,6 +118,11 @@ jobs:
|
|||
with:
|
||||
name: self-${{ github.sha }}
|
||||
|
||||
- name: Download rustc_tests metrics
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: rustc_tests-${{ github.sha }}
|
||||
|
||||
- name: Download ripgrep-13.0.0 metrics
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
|
@ -146,7 +151,7 @@ jobs:
|
|||
chmod 700 ~/.ssh
|
||||
|
||||
git clone --depth 1 git@github.com:rust-analyzer/metrics.git
|
||||
jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5]" build.json self.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json
|
||||
jq -s ".[0] * .[1] * .[2] * .[3] * .[4] * .[5] * .[6]" build.json self.json rustc_tests.json ripgrep-13.0.0.json webrender-2022.json diesel-1.4.8.json hyper-0.14.18.json -c >> metrics/metrics.json
|
||||
cd metrics
|
||||
git add .
|
||||
git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈
|
||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -684,7 +684,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"itertools",
|
||||
"limit",
|
||||
"line-index 0.1.0-pre.1",
|
||||
"line-index 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr",
|
||||
"nohash-hasher",
|
||||
"once_cell",
|
||||
|
@ -881,9 +881,7 @@ version = "0.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "line-index"
|
||||
version = "0.1.0-pre.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cad96769710c1745e11d4f940a8ff36000ade4bbada4285b001cb8aa2f745ce"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"nohash-hasher",
|
||||
"text-size",
|
||||
|
@ -891,7 +889,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "line-index"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67d61795376ae2683928c218fda7d7d7db136fd38c06b7552904667f0d55580a"
|
||||
dependencies = [
|
||||
"nohash-hasher",
|
||||
"text-size",
|
||||
|
@ -1545,6 +1545,7 @@ dependencies = [
|
|||
"triomphe",
|
||||
"vfs",
|
||||
"vfs-notify",
|
||||
"walkdir",
|
||||
"winapi",
|
||||
"xflags",
|
||||
"xshell",
|
||||
|
|
|
@ -85,7 +85,7 @@ rustc-dependencies = { path = "./crates/rustc-dependencies", version = "0.0.0" }
|
|||
proc-macro-test = { path = "./crates/proc-macro-test" }
|
||||
|
||||
# In-tree crates that are published separately and follow semver. See lib/README.md
|
||||
line-index = { version = "0.1.0-pre.1" }
|
||||
line-index = { version = "0.1.1" }
|
||||
la-arena = { version = "0.3.1" }
|
||||
lsp-server = { version = "0.7.4" }
|
||||
|
||||
|
|
|
@ -151,21 +151,26 @@ impl fmt::Debug for HirFileIdRepr {
|
|||
|
||||
impl From<FileId> for HirFileId {
|
||||
fn from(id: FileId) -> Self {
|
||||
assert!(id.index() < Self::MAX_FILE_ID);
|
||||
_ = Self::ASSERT_MAX_FILE_ID_IS_SAME;
|
||||
assert!(id.index() <= Self::MAX_HIR_FILE_ID, "FileId index {} is too large", id.index());
|
||||
HirFileId(id.index())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacroFileId> for HirFileId {
|
||||
fn from(MacroFileId { macro_call_id: MacroCallId(id) }: MacroFileId) -> Self {
|
||||
_ = Self::ASSERT_MAX_FILE_ID_IS_SAME;
|
||||
let id = id.as_u32();
|
||||
assert!(id < Self::MAX_FILE_ID);
|
||||
assert!(id <= Self::MAX_HIR_FILE_ID, "MacroCallId index {} is too large", id);
|
||||
HirFileId(id | Self::MACRO_FILE_TAG_MASK)
|
||||
}
|
||||
}
|
||||
|
||||
impl HirFileId {
|
||||
const MAX_FILE_ID: u32 = u32::MAX ^ Self::MACRO_FILE_TAG_MASK;
|
||||
const ASSERT_MAX_FILE_ID_IS_SAME: () =
|
||||
[()][(Self::MAX_HIR_FILE_ID != FileId::MAX_FILE_ID) as usize];
|
||||
|
||||
const MAX_HIR_FILE_ID: u32 = u32::MAX ^ Self::MACRO_FILE_TAG_MASK;
|
||||
const MACRO_FILE_TAG_MASK: u32 = 1 << 31;
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`.
|
||||
//!
|
||||
//! It was last synchronized with upstream commit e29821ff85a2a3000d226f99f62f89464028d5d6.
|
||||
//! It was last synchronized with upstream commit c3def263a44e07e09ae6d57abfc8650227fb4972.
|
||||
//!
|
||||
//! The macros were adjusted to only expand to the attribute name, since that is all we need to do
|
||||
//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to
|
||||
|
@ -240,7 +240,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
template!(List: "address, kcfi, memory, thread"), DuplicatesOk,
|
||||
experimental!(no_sanitize)
|
||||
),
|
||||
gated!(coverage, Normal, template!(Word, List: "on|off"), WarnFollowing, experimental!(coverage)),
|
||||
gated!(coverage, Normal, template!(Word, List: "on|off"), WarnFollowing, coverage_attribute, experimental!(coverage)),
|
||||
|
||||
ungated!(
|
||||
doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk
|
||||
|
@ -364,7 +364,6 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
allow_internal_unsafe, Normal, template!(Word), WarnFollowing,
|
||||
"allow_internal_unsafe side-steps the unsafe_code lint",
|
||||
),
|
||||
ungated!(rustc_safe_intrinsic, Normal, template!(Word), DuplicatesOk),
|
||||
rustc_attr!(rustc_allowed_through_unstable_modules, Normal, template!(Word), WarnFollowing,
|
||||
"rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \
|
||||
through unstable paths"),
|
||||
|
@ -453,6 +452,12 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
ErrorFollowing,
|
||||
INTERNAL_UNSTABLE
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_confusables, Normal,
|
||||
template!(List: r#""name1", "name2", ..."#),
|
||||
ErrorFollowing,
|
||||
INTERNAL_UNSTABLE,
|
||||
),
|
||||
// Enumerates "identity-like" conversion methods to suggest on type mismatch.
|
||||
rustc_attr!(
|
||||
rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
|
@ -488,6 +493,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
rustc_attr!(
|
||||
rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
// Ensure the argument to this function is &&str during const-check.
|
||||
rustc_attr!(
|
||||
rustc_const_panic_str, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Layout related:
|
||||
|
@ -520,6 +529,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
rustc_pass_by_value, Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_never_returns_null_ptr, Normal, template!(Word), ErrorFollowing,
|
||||
"#[rustc_never_returns_null_ptr] is used to mark functions returning non-null pointers."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true,
|
||||
"#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`."
|
||||
|
@ -533,7 +546,11 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
"#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_deny_explicit_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: false,
|
||||
rustc_deny_explicit_impl,
|
||||
AttributeType::Normal,
|
||||
template!(List: "implement_via_object = (true|false)"),
|
||||
ErrorFollowing,
|
||||
@only_local: true,
|
||||
"#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls"
|
||||
),
|
||||
rustc_attr!(
|
||||
|
@ -614,6 +631,10 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing,
|
||||
r#"`rustc_doc_primitive` is a rustc internal attribute"#,
|
||||
),
|
||||
rustc_attr!(
|
||||
rustc_safe_intrinsic, Normal, template!(Word), WarnFollowing,
|
||||
"the `#[rustc_safe_intrinsic]` attribute is used internally to mark intrinsics as safe"
|
||||
),
|
||||
|
||||
// ==========================================================================
|
||||
// Internal attributes, Testing:
|
||||
|
@ -625,13 +646,16 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_variance_of_opaques, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_hidden_type_of_opaques, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_abi, Normal, template!(List: "field1, field2, ..."), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(
|
||||
TEST, rustc_error, Normal,
|
||||
template!(Word, List: "span_delayed_bug_from_inside_query"), WarnFollowingWordOnly
|
||||
),
|
||||
rustc_attr!(TEST, rustc_dump_user_substs, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_dump_user_args, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing),
|
||||
rustc_attr!(
|
||||
TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
//! node for a *child*, and get its hir.
|
||||
|
||||
use either::Either;
|
||||
use hir_expand::HirFileId;
|
||||
use syntax::ast::HasDocComments;
|
||||
use hir_expand::{attrs::collect_attrs, HirFileId};
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
|
@ -118,8 +117,8 @@ impl ChildBySource for ItemScope {
|
|||
|(ast_id, calls)| {
|
||||
let adt = ast_id.to_node(db.upcast());
|
||||
calls.for_each(|(attr_id, call_id, calls)| {
|
||||
if let Some(Either::Left(attr)) =
|
||||
adt.doc_comments_and_attrs().nth(attr_id.ast_index())
|
||||
if let Some((_, Either::Left(attr))) =
|
||||
collect_attrs(&adt).nth(attr_id.ast_index())
|
||||
{
|
||||
res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into()));
|
||||
}
|
||||
|
|
|
@ -222,11 +222,10 @@ impl GenericParams {
|
|||
let module = loc.container.module(db);
|
||||
let func_data = db.function_data(id);
|
||||
|
||||
// Don't create an `Expander` nor call `loc.source(db)` if not needed since this
|
||||
// causes a reparse after the `ItemTree` has been created.
|
||||
let mut expander = Lazy::new(|| {
|
||||
(module.def_map(db), Expander::new(db, loc.source(db).file_id, module))
|
||||
});
|
||||
// Don't create an `Expander` if not needed since this
|
||||
// could cause a reparse after the `ItemTree` has been created due to the spanmap.
|
||||
let mut expander =
|
||||
Lazy::new(|| (module.def_map(db), Expander::new(db, loc.id.file_id(), module)));
|
||||
for param in func_data.params.iter() {
|
||||
generic_params.fill_implicit_impl_trait_args(db, &mut expander, param);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use indexmap::IndexMap;
|
|||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashSet, FxHasher};
|
||||
use smallvec::SmallVec;
|
||||
use stdx::format_to;
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
|
@ -53,13 +54,25 @@ pub struct ImportMap {
|
|||
fst: fst::Map<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
enum IsTraitAssocItem {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
impl ImportMap {
|
||||
pub fn dump(&self, db: &dyn DefDatabase) -> String {
|
||||
let mut out = String::new();
|
||||
for (k, v) in self.map.iter() {
|
||||
format_to!(out, "{:?} ({:?}) -> ", k, v.1);
|
||||
for v in &v.0 {
|
||||
format_to!(out, "{}:{:?}, ", v.name.display(db.upcast()), v.container);
|
||||
}
|
||||
format_to!(out, "\n");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
|
||||
let _p = profile::span("import_map_query");
|
||||
|
||||
|
@ -68,26 +81,31 @@ impl ImportMap {
|
|||
let mut importables: Vec<_> = map
|
||||
.iter()
|
||||
// We've only collected items, whose name cannot be tuple field.
|
||||
.flat_map(|(&item, (info, _))| {
|
||||
info.iter()
|
||||
.map(move |info| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
|
||||
.flat_map(|(&item, (info, is_assoc))| {
|
||||
info.iter().map(move |info| {
|
||||
(item, *is_assoc, info.name.as_str().unwrap().to_ascii_lowercase())
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
|
||||
importables.sort_by(|(_, l_is_assoc, lhs_name), (_, r_is_assoc, rhs_name)| {
|
||||
lhs_name.cmp(rhs_name).then_with(|| l_is_assoc.cmp(r_is_assoc))
|
||||
});
|
||||
importables.dedup();
|
||||
|
||||
// Build the FST, taking care not to insert duplicate values.
|
||||
let mut builder = fst::MapBuilder::memory();
|
||||
let iter =
|
||||
importables.iter().enumerate().dedup_by(|(_, (_, lhs)), (_, (_, rhs))| lhs == rhs);
|
||||
for (start_idx, (_, name)) in iter {
|
||||
let iter = importables
|
||||
.iter()
|
||||
.enumerate()
|
||||
.dedup_by(|(_, (_, _, lhs)), (_, (_, _, rhs))| lhs == rhs);
|
||||
for (start_idx, (_, _, name)) in iter {
|
||||
let _ = builder.insert(name, start_idx as u64);
|
||||
}
|
||||
|
||||
Arc::new(ImportMap {
|
||||
map,
|
||||
fst: builder.into_map(),
|
||||
importables: importables.into_iter().map(|(item, _)| item).collect(),
|
||||
importables: importables.into_iter().map(|(item, _, _)| item).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -328,20 +346,20 @@ impl Query {
|
|||
}
|
||||
|
||||
/// Checks whether the import map entry matches the query.
|
||||
fn import_matches(
|
||||
&self,
|
||||
db: &dyn DefDatabase,
|
||||
import: &ImportInfo,
|
||||
enforce_lowercase: bool,
|
||||
) -> bool {
|
||||
fn import_matches(&self, import: &ImportInfo, enforce_lowercase: bool) -> bool {
|
||||
let _p = profile::span("import_map::Query::import_matches");
|
||||
|
||||
// FIXME: Can we get rid of the alloc here?
|
||||
let mut input = import.name.display(db.upcast()).to_string();
|
||||
let input = import.name.to_smol_str();
|
||||
let mut _s_slot;
|
||||
let case_insensitive = enforce_lowercase || !self.case_sensitive;
|
||||
if case_insensitive {
|
||||
input.make_ascii_lowercase();
|
||||
}
|
||||
let input = if case_insensitive {
|
||||
_s_slot = String::from(input);
|
||||
_s_slot.make_ascii_lowercase();
|
||||
&*_s_slot
|
||||
} else {
|
||||
&*input
|
||||
};
|
||||
|
||||
let query_string = if case_insensitive { &self.lowercased } else { &self.query };
|
||||
|
||||
|
@ -351,7 +369,7 @@ impl Query {
|
|||
SearchMode::Fuzzy => {
|
||||
let mut input_chars = input.chars();
|
||||
for query_char in query_string.chars() {
|
||||
if input_chars.find(|&it| it == query_char).is_none() {
|
||||
if !input_chars.any(|it| it == query_char) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -372,6 +390,7 @@ pub fn search_dependencies(
|
|||
let _p = profile::span("search_dependencies").detail(|| format!("{query:?}"));
|
||||
|
||||
let graph = db.crate_graph();
|
||||
|
||||
let import_maps: Vec<_> =
|
||||
graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
|
||||
|
||||
|
@ -386,22 +405,23 @@ pub fn search_dependencies(
|
|||
|
||||
let mut res = FxHashSet::default();
|
||||
let mut common_importable_data_scratch = vec![];
|
||||
// FIXME: Improve this, its rather unreadable and does duplicate amount of work
|
||||
while let Some((_, indexed_values)) = stream.next() {
|
||||
for &IndexedValue { index, value } in indexed_values {
|
||||
let import_map = &import_maps[index];
|
||||
let importables @ [importable, ..] = &import_map.importables[value as usize..] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let &(ref importable_data, is_trait_assoc_item) = &import_map.map[importable];
|
||||
if !query.matches_assoc_mode(is_trait_assoc_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch all the known names of this importable item (to handle import aliases/renames)
|
||||
common_importable_data_scratch.extend(
|
||||
importable_data
|
||||
.iter()
|
||||
.filter(|&info| query.import_matches(db, info, true))
|
||||
.filter(|&info| query.import_matches(info, true))
|
||||
// Name shared by the importable items in this group.
|
||||
.map(|info| info.name.to_smol_str()),
|
||||
);
|
||||
|
@ -415,6 +435,7 @@ pub fn search_dependencies(
|
|||
common_importable_data_scratch.drain(..).flat_map(|common_importable_name| {
|
||||
// Add the items from this name group. Those are all subsequent items in
|
||||
// `importables` whose name match `common_importable_name`.
|
||||
|
||||
importables
|
||||
.iter()
|
||||
.copied()
|
||||
|
@ -430,11 +451,8 @@ pub fn search_dependencies(
|
|||
.filter(move |item| {
|
||||
!query.case_sensitive || {
|
||||
// we've already checked the common importables name case-insensitively
|
||||
let &(ref import_infos, assoc_mode) = &import_map.map[item];
|
||||
query.matches_assoc_mode(assoc_mode)
|
||||
&& import_infos
|
||||
.iter()
|
||||
.any(|info| query.import_matches(db, info, false))
|
||||
let &(ref import_infos, _) = &import_map.map[item];
|
||||
import_infos.iter().any(|info| query.import_matches(info, false))
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -106,11 +106,6 @@ impl ItemTree {
|
|||
pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) -> Arc<ItemTree> {
|
||||
let _p = profile::span("file_item_tree_query").detail(|| format!("{file_id:?}"));
|
||||
let syntax = db.parse_or_expand(file_id);
|
||||
if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax)
|
||||
{
|
||||
// FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic
|
||||
return Default::default();
|
||||
}
|
||||
|
||||
let ctx = lower::Ctx::new(db, file_id);
|
||||
let mut top_attrs = None;
|
||||
|
@ -129,6 +124,9 @@ impl ItemTree {
|
|||
ctx.lower_macro_stmts(stmts)
|
||||
},
|
||||
_ => {
|
||||
if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax) {
|
||||
return Default::default();
|
||||
}
|
||||
panic!("cannot create item tree for file {file_id:?} from {syntax:?} {syntax}");
|
||||
},
|
||||
}
|
||||
|
|
|
@ -569,6 +569,8 @@ pub struct ConstBlockLoc {
|
|||
pub root: hir::ExprId,
|
||||
}
|
||||
|
||||
/// Something that holds types, required for the current const arg lowering implementation as they
|
||||
/// need to be able to query where they are defined.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum TypeOwnerId {
|
||||
FunctionId(FunctionId),
|
||||
|
@ -581,9 +583,6 @@ pub enum TypeOwnerId {
|
|||
TypeAliasId(TypeAliasId),
|
||||
ImplId(ImplId),
|
||||
EnumVariantId(EnumVariantId),
|
||||
// FIXME(const-generic-body): ModuleId should not be a type owner. This needs to be fixed to make `TypeOwnerId` actually
|
||||
// useful for assigning ids to in type consts.
|
||||
ModuleId(ModuleId),
|
||||
}
|
||||
|
||||
impl TypeOwnerId {
|
||||
|
@ -597,9 +596,7 @@ impl TypeOwnerId {
|
|||
TypeOwnerId::TypeAliasId(it) => GenericDefId::TypeAliasId(it),
|
||||
TypeOwnerId::ImplId(it) => GenericDefId::ImplId(it),
|
||||
TypeOwnerId::EnumVariantId(it) => GenericDefId::EnumVariantId(it),
|
||||
TypeOwnerId::InTypeConstId(_) | TypeOwnerId::ModuleId(_) | TypeOwnerId::StaticId(_) => {
|
||||
return None
|
||||
}
|
||||
TypeOwnerId::InTypeConstId(_) | TypeOwnerId::StaticId(_) => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -614,8 +611,7 @@ impl_from!(
|
|||
TraitAliasId,
|
||||
TypeAliasId,
|
||||
ImplId,
|
||||
EnumVariantId,
|
||||
ModuleId
|
||||
EnumVariantId
|
||||
for TypeOwnerId
|
||||
);
|
||||
|
||||
|
@ -713,12 +709,15 @@ pub struct InTypeConstLoc {
|
|||
pub id: AstId<ast::ConstArg>,
|
||||
/// The thing this const arg appears in
|
||||
pub owner: TypeOwnerId,
|
||||
pub thing: Box<dyn OpaqueInternableThing>,
|
||||
// FIXME(const-generic-body): The expected type should not be
|
||||
pub expected_ty: Box<dyn OpaqueInternableThing>,
|
||||
}
|
||||
|
||||
impl PartialEq for InTypeConstLoc {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.owner == other.owner && &*self.thing == &*other.thing
|
||||
self.id == other.id
|
||||
&& self.owner == other.owner
|
||||
&& &*self.expected_ty == &*other.expected_ty
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1041,7 +1040,6 @@ impl HasModule for TypeOwnerId {
|
|||
TypeOwnerId::TypeAliasId(it) => it.lookup(db).module(db),
|
||||
TypeOwnerId::ImplId(it) => it.lookup(db).container,
|
||||
TypeOwnerId::EnumVariantId(it) => it.parent.lookup(db).container,
|
||||
TypeOwnerId::ModuleId(it) => *it,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,37 +13,97 @@ fn test_vec() {
|
|||
check(
|
||||
r#"
|
||||
macro_rules! vec {
|
||||
($($item:expr),*) => {{
|
||||
let mut v = Vec::new();
|
||||
$( v.push($item); )*
|
||||
v
|
||||
}};
|
||||
() => (
|
||||
$crate::__rust_force_expr!($crate::vec::Vec::new())
|
||||
);
|
||||
($elem:expr; $n:expr) => (
|
||||
$crate::__rust_force_expr!($crate::vec::from_elem($elem, $n))
|
||||
);
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::__rust_force_expr!(<[_]>::into_vec(
|
||||
// This rustc_box is not required, but it produces a dramatic improvement in compile
|
||||
// time when constructing arrays with many elements.
|
||||
#[rustc_box]
|
||||
$crate::boxed::Box::new([$($x),+])
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! __rust_force_expr {
|
||||
($e:expr) => {
|
||||
$e
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
vec!();
|
||||
vec![1u32,2];
|
||||
vec![a.];
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! vec {
|
||||
($($item:expr),*) => {{
|
||||
let mut v = Vec::new();
|
||||
$( v.push($item); )*
|
||||
v
|
||||
}};
|
||||
() => (
|
||||
$crate::__rust_force_expr!($crate::vec::Vec::new())
|
||||
);
|
||||
($elem:expr; $n:expr) => (
|
||||
$crate::__rust_force_expr!($crate::vec::from_elem($elem, $n))
|
||||
);
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::__rust_force_expr!(<[_]>::into_vec(
|
||||
// This rustc_box is not required, but it produces a dramatic improvement in compile
|
||||
// time when constructing arrays with many elements.
|
||||
#[rustc_box]
|
||||
$crate::boxed::Box::new([$($x),+])
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! __rust_force_expr {
|
||||
($e:expr) => {
|
||||
$e
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
{
|
||||
let mut v = Vec::new();
|
||||
v
|
||||
$crate::__rust_force_expr!($crate:: vec:: Vec:: new());
|
||||
$crate::__rust_force_expr!(<[_]>:: into_vec(#[rustc_box]$crate:: boxed:: Box:: new([1u32, 2])));
|
||||
/* error: expected Expr */$crate::__rust_force_expr!($crate:: vec:: from_elem((a.), $n));
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
// FIXME we should ahev testing infra for multi level expansion tests
|
||||
check(
|
||||
r#"
|
||||
macro_rules! __rust_force_expr {
|
||||
($e:expr) => {
|
||||
$e
|
||||
};
|
||||
{
|
||||
let mut v = Vec::new();
|
||||
v.push(1u32);
|
||||
v.push(2);
|
||||
v
|
||||
}
|
||||
|
||||
fn main() {
|
||||
__rust_force_expr!(crate:: vec:: Vec:: new());
|
||||
__rust_force_expr!(<[_]>:: into_vec(#[rustc_box] crate:: boxed:: Box:: new([1u32, 2])));
|
||||
__rust_force_expr/*+errors*/!(crate:: vec:: from_elem((a.), $n));
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
macro_rules! __rust_force_expr {
|
||||
($e:expr) => {
|
||||
$e
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
(crate ::vec::Vec::new());
|
||||
(<[_]>::into_vec(#[rustc_box] crate ::boxed::Box::new([1u32, 2])));
|
||||
/* error: expected Expr *//* parse error: expected field name or number */
|
||||
/* parse error: expected expression */
|
||||
/* parse error: expected R_PAREN */
|
||||
/* parse error: expected COMMA */
|
||||
/* parse error: expected expression, item or let statement */
|
||||
(crate ::vec::from_elem((a.), $n));
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -589,6 +589,16 @@ impl Resolver {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn type_owner(&self) -> Option<TypeOwnerId> {
|
||||
self.scopes().find_map(|scope| match scope {
|
||||
Scope::BlockScope(_) => None,
|
||||
&Scope::GenericParams { def, .. } => Some(def.into()),
|
||||
&Scope::ImplDefScope(id) => Some(id.into()),
|
||||
&Scope::AdtScope(adt) => Some(adt.into()),
|
||||
Scope::ExprScope(it) => Some(it.owner.into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn impl_def(&self) -> Option<ImplId> {
|
||||
self.scopes().find_map(|scope| match scope {
|
||||
Scope::ImplDefScope(def) => Some(*def),
|
||||
|
@ -1079,7 +1089,6 @@ impl HasResolver for TypeOwnerId {
|
|||
TypeOwnerId::TypeAliasId(it) => it.resolver(db),
|
||||
TypeOwnerId::ImplId(it) => it.resolver(db),
|
||||
TypeOwnerId::EnumVariantId(it) => it.resolver(db),
|
||||
TypeOwnerId::ModuleId(it) => it.resolver(db),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,17 +10,17 @@ use limit::Limit;
|
|||
use mbe::{syntax_node_to_token_tree, ValueResult};
|
||||
use rustc_hash::FxHashSet;
|
||||
use syntax::{
|
||||
ast::{self, HasAttrs, HasDocComments},
|
||||
ast::{self, HasAttrs},
|
||||
AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
ast_id_map::AstIdMap,
|
||||
attrs::RawAttrs,
|
||||
attrs::{collect_attrs, RawAttrs},
|
||||
builtin_attr_macro::pseudo_derive_attr_expansion,
|
||||
builtin_fn_macro::EagerExpander,
|
||||
fixup::{self, SyntaxFixupUndoInfo},
|
||||
fixup::{self, reverse_fixups, SyntaxFixupUndoInfo},
|
||||
hygiene::{apply_mark, SyntaxContextData, Transparency},
|
||||
span::{RealSpanMap, SpanMap, SpanMapRef},
|
||||
tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo,
|
||||
|
@ -216,9 +216,9 @@ pub fn expand_speculative(
|
|||
// Attributes may have an input token tree, build the subtree and map for this as well
|
||||
// then try finding a token id for our token if it is inside this input subtree.
|
||||
let item = ast::Item::cast(speculative_args.clone())?;
|
||||
item.doc_comments_and_attrs()
|
||||
collect_attrs(&item)
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.and_then(Either::left)
|
||||
.and_then(|x| Either::left(x.1))
|
||||
}?;
|
||||
match attr.token_tree() {
|
||||
Some(token_tree) => {
|
||||
|
@ -421,6 +421,15 @@ fn macro_arg(
|
|||
syntax::NodeOrToken::Token(_) => true,
|
||||
});
|
||||
fixups.remove.extend(censor);
|
||||
{
|
||||
let mut tt = mbe::syntax_node_to_token_tree_modified(
|
||||
&syntax,
|
||||
map.as_ref(),
|
||||
fixups.append.clone(),
|
||||
fixups.remove.clone(),
|
||||
);
|
||||
reverse_fixups(&mut tt, &fixups.undo_info);
|
||||
}
|
||||
(
|
||||
mbe::syntax_node_to_token_tree_modified(
|
||||
&syntax,
|
||||
|
@ -479,10 +488,9 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy
|
|||
MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => return None,
|
||||
MacroCallKind::Attr { invoc_attr_index, .. } => {
|
||||
cov_mark::hit!(attribute_macro_attr_censoring);
|
||||
ast::Item::cast(node.clone())?
|
||||
.doc_comments_and_attrs()
|
||||
collect_attrs(&ast::Item::cast(node.clone())?)
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.and_then(Either::left)
|
||||
.and_then(|x| Either::left(x.1))
|
||||
.map(|attr| attr.syntax().clone())
|
||||
.into_iter()
|
||||
.collect()
|
||||
|
|
|
@ -88,7 +88,7 @@ pub fn expand_eager_macro_input(
|
|||
let loc = MacroCallLoc {
|
||||
def,
|
||||
krate,
|
||||
eager: Some(Box::new(EagerCallInfo { arg: Arc::new(subtree), arg_id, error: err.clone() })),
|
||||
eager: Some(Arc::new(EagerCallInfo { arg: Arc::new(subtree), arg_id, error: err.clone() })),
|
||||
kind: MacroCallKind::FnLike { ast_id: call_id, expand_to },
|
||||
call_site,
|
||||
};
|
||||
|
|
|
@ -8,12 +8,13 @@ use base_db::{
|
|||
use la_arena::RawIdx;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::SmallVec;
|
||||
use stdx::never;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, HasLoopBody},
|
||||
match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
use tt::Spacing;
|
||||
use tt::{Spacing, Span};
|
||||
|
||||
use crate::{
|
||||
span::SpanMapRef,
|
||||
|
@ -45,19 +46,20 @@ impl SyntaxFixupUndoInfo {
|
|||
// replacement -> censor + append
|
||||
// append -> insert a fake node, here we need to assemble some dummy span that we can figure out how
|
||||
// to remove later
|
||||
const FIXUP_DUMMY_FILE: FileId = FileId::from_raw(FileId::MAX_FILE_ID);
|
||||
const FIXUP_DUMMY_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(RawIdx::from_u32(!0));
|
||||
const FIXUP_DUMMY_RANGE: TextRange = TextRange::empty(TextSize::new(0));
|
||||
const FIXUP_DUMMY_RANGE_END: TextSize = TextSize::new(!0);
|
||||
|
||||
pub(crate) fn fixup_syntax(span_map: SpanMapRef<'_>, node: &SyntaxNode) -> SyntaxFixups {
|
||||
let mut append = FxHashMap::<SyntaxElement, _>::default();
|
||||
let mut remove = FxHashSet::<SyntaxNode>::default();
|
||||
let mut preorder = node.preorder();
|
||||
let mut original = Vec::new();
|
||||
let dummy_range = TextRange::empty(TextSize::new(0));
|
||||
let dummy_range = FIXUP_DUMMY_RANGE;
|
||||
// we use a file id of `FileId(!0)` to signal a fake node, and the text range's start offset as
|
||||
// the index into the replacement vec but only if the end points to !0
|
||||
let dummy_anchor = SpanAnchor {
|
||||
file_id: FileId::from_raw(!0),
|
||||
ast_id: ErasedFileAstId::from_raw(RawIdx::from(!0)),
|
||||
};
|
||||
let dummy_anchor = SpanAnchor { file_id: FIXUP_DUMMY_FILE, ast_id: FIXUP_DUMMY_AST_ID };
|
||||
let fake_span = |range| SpanData {
|
||||
range: dummy_range,
|
||||
anchor: dummy_anchor,
|
||||
|
@ -76,7 +78,7 @@ pub(crate) fn fixup_syntax(span_map: SpanMapRef<'_>, node: &SyntaxNode) -> Synta
|
|||
let replacement = Leaf::Ident(Ident {
|
||||
text: "__ra_fixup".into(),
|
||||
span: SpanData {
|
||||
range: TextRange::new(TextSize::new(idx), TextSize::new(!0)),
|
||||
range: TextRange::new(TextSize::new(idx), FIXUP_DUMMY_RANGE_END),
|
||||
anchor: dummy_anchor,
|
||||
ctx: span_map.span_for_range(node_range).ctx,
|
||||
},
|
||||
|
@ -299,6 +301,13 @@ fn has_error_to_handle(node: &SyntaxNode) -> bool {
|
|||
pub(crate) fn reverse_fixups(tt: &mut Subtree, undo_info: &SyntaxFixupUndoInfo) {
|
||||
let Some(undo_info) = undo_info.original.as_deref() else { return };
|
||||
let undo_info = &**undo_info;
|
||||
if never!(
|
||||
tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
|
||||
|| tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
|
||||
) {
|
||||
tt.delimiter.close = SpanData::DUMMY;
|
||||
tt.delimiter.open = SpanData::DUMMY;
|
||||
}
|
||||
reverse_fixups_(tt, undo_info);
|
||||
}
|
||||
|
||||
|
@ -310,17 +319,28 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
|
|||
.filter(|tt| match tt {
|
||||
tt::TokenTree::Leaf(leaf) => {
|
||||
let span = leaf.span();
|
||||
span.anchor.file_id != FileId::from_raw(!0) || span.range.end() == TextSize::new(!0)
|
||||
let is_real_leaf = span.anchor.file_id != FIXUP_DUMMY_FILE;
|
||||
let is_replaced_node = span.range.end() == FIXUP_DUMMY_RANGE_END;
|
||||
is_real_leaf || is_replaced_node
|
||||
}
|
||||
tt::TokenTree::Subtree(_) => true,
|
||||
})
|
||||
.flat_map(|tt| match tt {
|
||||
tt::TokenTree::Subtree(mut tt) => {
|
||||
if tt.delimiter.close.anchor.file_id == FIXUP_DUMMY_FILE
|
||||
|| tt.delimiter.open.anchor.file_id == FIXUP_DUMMY_FILE
|
||||
{
|
||||
// Even though fixup never creates subtrees with fixup spans, the old proc-macro server
|
||||
// might copy them if the proc-macro asks for it, so we need to filter those out
|
||||
// here as well.
|
||||
return SmallVec::new_const();
|
||||
}
|
||||
reverse_fixups_(&mut tt, undo_info);
|
||||
SmallVec::from_const([tt.into()])
|
||||
}
|
||||
tt::TokenTree::Leaf(leaf) => {
|
||||
if leaf.span().anchor.file_id == FileId::from_raw(!0) {
|
||||
if leaf.span().anchor.file_id == FIXUP_DUMMY_FILE {
|
||||
// we have a fake node here, we need to replace it again with the original
|
||||
let original = undo_info[u32::from(leaf.span().range.start()) as usize].clone();
|
||||
if original.delimiter.kind == tt::DelimiterKind::Invisible {
|
||||
original.token_trees.into()
|
||||
|
@ -328,6 +348,7 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
|
|||
SmallVec::from_const([original.into()])
|
||||
}
|
||||
} else {
|
||||
// just a normal leaf
|
||||
SmallVec::from_const([leaf.into()])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ pub mod span;
|
|||
pub mod files;
|
||||
mod fixup;
|
||||
|
||||
use attrs::collect_attrs;
|
||||
use triomphe::Arc;
|
||||
|
||||
use std::{fmt, hash::Hash};
|
||||
|
@ -32,7 +33,7 @@ use base_db::{
|
|||
};
|
||||
use either::Either;
|
||||
use syntax::{
|
||||
ast::{self, AstNode, HasDocComments},
|
||||
ast::{self, AstNode},
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
|
||||
|
@ -116,7 +117,7 @@ pub struct MacroCallLoc {
|
|||
pub krate: CrateId,
|
||||
/// Some if this is a macro call for an eager macro. Note that this is `None`
|
||||
/// for the eager input macro file.
|
||||
eager: Option<Box<EagerCallInfo>>,
|
||||
eager: Option<Arc<EagerCallInfo>>,
|
||||
pub kind: MacroCallKind,
|
||||
pub call_site: SyntaxContextId,
|
||||
}
|
||||
|
@ -438,9 +439,9 @@ impl MacroCallLoc {
|
|||
MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
|
||||
// FIXME: handle `cfg_attr`
|
||||
ast_id.with_value(ast_id.to_node(db)).map(|it| {
|
||||
it.doc_comments_and_attrs()
|
||||
collect_attrs(&it)
|
||||
.nth(derive_attr_index.ast_index())
|
||||
.and_then(|it| match it {
|
||||
.and_then(|it| match it.1 {
|
||||
Either::Left(attr) => Some(attr.syntax().clone()),
|
||||
Either::Right(_) => None,
|
||||
})
|
||||
|
@ -451,9 +452,9 @@ impl MacroCallLoc {
|
|||
if self.def.is_attribute_derive() {
|
||||
// FIXME: handle `cfg_attr`
|
||||
ast_id.with_value(ast_id.to_node(db)).map(|it| {
|
||||
it.doc_comments_and_attrs()
|
||||
collect_attrs(&it)
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.and_then(|it| match it {
|
||||
.and_then(|it| match it.1 {
|
||||
Either::Left(attr) => Some(attr.syntax().clone()),
|
||||
Either::Right(_) => None,
|
||||
})
|
||||
|
@ -549,24 +550,24 @@ impl MacroCallKind {
|
|||
MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
|
||||
// FIXME: should be the range of the macro name, not the whole derive
|
||||
// FIXME: handle `cfg_attr`
|
||||
ast_id
|
||||
.to_node(db)
|
||||
.doc_comments_and_attrs()
|
||||
collect_attrs(&ast_id.to_node(db))
|
||||
.nth(derive_attr_index.ast_index())
|
||||
.expect("missing derive")
|
||||
.1
|
||||
.expect_left("derive is a doc comment?")
|
||||
.syntax()
|
||||
.text_range()
|
||||
}
|
||||
// FIXME: handle `cfg_attr`
|
||||
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => ast_id
|
||||
.to_node(db)
|
||||
.doc_comments_and_attrs()
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.expect("missing attribute")
|
||||
.expect_left("attribute macro is a doc comment?")
|
||||
.syntax()
|
||||
.text_range(),
|
||||
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
|
||||
collect_attrs(&ast_id.to_node(db))
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.expect("missing attribute")
|
||||
.1
|
||||
.expect_left("attribute macro is a doc comment?")
|
||||
.syntax()
|
||||
.text_range()
|
||||
}
|
||||
};
|
||||
|
||||
FileRange { range, file_id }
|
||||
|
@ -737,11 +738,9 @@ impl ExpansionInfo {
|
|||
let attr_input_or_mac_def = def.or_else(|| match loc.kind {
|
||||
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
|
||||
// FIXME: handle `cfg_attr`
|
||||
let tt = ast_id
|
||||
.to_node(db)
|
||||
.doc_comments_and_attrs()
|
||||
let tt = collect_attrs(&ast_id.to_node(db))
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.and_then(Either::left)?
|
||||
.and_then(|x| Either::left(x.1))?
|
||||
.token_tree()?;
|
||||
Some(InFile::new(ast_id.file_id, tt))
|
||||
}
|
||||
|
|
|
@ -75,27 +75,40 @@ pub struct RealSpanMap {
|
|||
/// Invariant: Sorted vec over TextSize
|
||||
// FIXME: SortedVec<(TextSize, ErasedFileAstId)>?
|
||||
pairs: Box<[(TextSize, ErasedFileAstId)]>,
|
||||
end: TextSize,
|
||||
}
|
||||
|
||||
impl RealSpanMap {
|
||||
/// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id).
|
||||
pub fn absolute(file_id: FileId) -> Self {
|
||||
RealSpanMap { file_id, pairs: Box::from([(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]) }
|
||||
RealSpanMap {
|
||||
file_id,
|
||||
pairs: Box::from([(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)]),
|
||||
end: TextSize::new(!0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(db: &dyn ExpandDatabase, file_id: FileId) -> Self {
|
||||
let mut pairs = vec![(TextSize::new(0), ROOT_ERASED_FILE_AST_ID)];
|
||||
let ast_id_map = db.ast_id_map(file_id.into());
|
||||
pairs.extend(
|
||||
db.parse(file_id)
|
||||
.tree()
|
||||
.items()
|
||||
.map(|item| (item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase())),
|
||||
);
|
||||
RealSpanMap { file_id, pairs: pairs.into_boxed_slice() }
|
||||
let tree = db.parse(file_id).tree();
|
||||
pairs
|
||||
.extend(tree.items().map(|item| {
|
||||
(item.syntax().text_range().start(), ast_id_map.ast_id(&item).erase())
|
||||
}));
|
||||
RealSpanMap {
|
||||
file_id,
|
||||
pairs: pairs.into_boxed_slice(),
|
||||
end: tree.syntax().text_range().end(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span_for_range(&self, range: TextRange) -> SpanData {
|
||||
assert!(
|
||||
range.end() <= self.end,
|
||||
"range {range:?} goes beyond the end of the file {:?}",
|
||||
self.end
|
||||
);
|
||||
let start = range.start();
|
||||
let idx = self
|
||||
.pairs
|
||||
|
|
|
@ -113,7 +113,7 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer
|
|||
// FIXME(const-generic-body): We should not get the return type in this way.
|
||||
ctx.return_ty = c
|
||||
.lookup(db.upcast())
|
||||
.thing
|
||||
.expected_ty
|
||||
.box_any()
|
||||
.downcast::<InTypeConstIdMetadata>()
|
||||
.unwrap()
|
||||
|
|
|
@ -262,7 +262,7 @@ impl InferenceContext<'_> {
|
|||
fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty {
|
||||
let mut expected = self.resolve_ty_shallow(expected);
|
||||
|
||||
if is_non_ref_pat(self.body, pat) {
|
||||
if self.is_non_ref_pat(self.body, pat) {
|
||||
let mut pat_adjustments = Vec::new();
|
||||
while let Some((inner, _lifetime, mutability)) = expected.as_reference() {
|
||||
pat_adjustments.push(expected.clone());
|
||||
|
@ -496,24 +496,28 @@ impl InferenceContext<'_> {
|
|||
|
||||
self.infer_expr(expr, &Expectation::has_type(expected.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
|
||||
match &body[pat] {
|
||||
Pat::Tuple { .. }
|
||||
| Pat::TupleStruct { .. }
|
||||
| Pat::Record { .. }
|
||||
| Pat::Range { .. }
|
||||
| Pat::Slice { .. } => true,
|
||||
Pat::Or(pats) => pats.iter().all(|p| is_non_ref_pat(body, *p)),
|
||||
// FIXME: ConstBlock/Path/Lit might actually evaluate to ref, but inference is unimplemented.
|
||||
Pat::Path(..) => true,
|
||||
Pat::ConstBlock(..) => true,
|
||||
Pat::Lit(expr) => !matches!(
|
||||
body[*expr],
|
||||
Expr::Literal(Literal::String(..) | Literal::CString(..) | Literal::ByteString(..))
|
||||
),
|
||||
Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
|
||||
fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool {
|
||||
match &body[pat] {
|
||||
Pat::Tuple { .. }
|
||||
| Pat::TupleStruct { .. }
|
||||
| Pat::Record { .. }
|
||||
| Pat::Range { .. }
|
||||
| Pat::Slice { .. } => true,
|
||||
Pat::Or(pats) => pats.iter().all(|p| self.is_non_ref_pat(body, *p)),
|
||||
Pat::Path(p) => {
|
||||
let v = self.resolve_value_path_inner(p, pat.into());
|
||||
v.is_some_and(|x| !matches!(x.0, hir_def::resolver::ValueNs::ConstId(_)))
|
||||
}
|
||||
Pat::ConstBlock(..) => false,
|
||||
Pat::Lit(expr) => !matches!(
|
||||
body[*expr],
|
||||
Expr::Literal(Literal::String(..) | Literal::CString(..) | Literal::ByteString(..))
|
||||
),
|
||||
Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,33 +40,7 @@ impl InferenceContext<'_> {
|
|||
}
|
||||
|
||||
fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<ValuePathResolution> {
|
||||
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
|
||||
let last = path.segments().last()?;
|
||||
|
||||
// Don't use `self.make_ty()` here as we need `orig_ns`.
|
||||
let ctx =
|
||||
crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into());
|
||||
let (ty, orig_ns) = ctx.lower_ty_ext(type_ref);
|
||||
let ty = self.table.insert_type_vars(ty);
|
||||
let ty = self.table.normalize_associated_types_in(ty);
|
||||
|
||||
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
|
||||
let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
|
||||
let ty = self.table.insert_type_vars(ty);
|
||||
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 {
|
||||
// FIXME: report error, unresolved first path segment
|
||||
let value_or_partial =
|
||||
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?;
|
||||
|
||||
match value_or_partial {
|
||||
ResolveValueResult::ValueNs(it, _) => (it, None),
|
||||
ResolveValueResult::Partial(def, remaining_index, _) => self
|
||||
.resolve_assoc_item(def, path, remaining_index, id)
|
||||
.map(|(it, substs)| (it, Some(substs)))?,
|
||||
}
|
||||
};
|
||||
let (value, self_subst) = self.resolve_value_path_inner(path, id)?;
|
||||
|
||||
let value_def = match value {
|
||||
ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) {
|
||||
|
@ -144,6 +118,41 @@ impl InferenceContext<'_> {
|
|||
Some(ValuePathResolution::GenericDef(value_def, generic_def, substs))
|
||||
}
|
||||
|
||||
pub(super) fn resolve_value_path_inner(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
id: ExprOrPatId,
|
||||
) -> Option<(ValueNs, Option<chalk_ir::Substitution<Interner>>)> {
|
||||
let (value, self_subst) = if let Some(type_ref) = path.type_anchor() {
|
||||
let last = path.segments().last()?;
|
||||
|
||||
// Don't use `self.make_ty()` here as we need `orig_ns`.
|
||||
let ctx =
|
||||
crate::lower::TyLoweringContext::new(self.db, &self.resolver, self.owner.into());
|
||||
let (ty, orig_ns) = ctx.lower_ty_ext(type_ref);
|
||||
let ty = self.table.insert_type_vars(ty);
|
||||
let ty = self.table.normalize_associated_types_in(ty);
|
||||
|
||||
let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1);
|
||||
let (ty, _) = ctx.lower_ty_relative_path(ty, orig_ns, remaining_segments_for_ty);
|
||||
let ty = self.table.insert_type_vars(ty);
|
||||
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 {
|
||||
// FIXME: report error, unresolved first path segment
|
||||
let value_or_partial =
|
||||
self.resolver.resolve_path_in_value_ns(self.db.upcast(), path)?;
|
||||
|
||||
match value_or_partial {
|
||||
ResolveValueResult::ValueNs(it, _) => (it, None),
|
||||
ResolveValueResult::Partial(def, remaining_index, _) => self
|
||||
.resolve_assoc_item(def, path, remaining_index, id)
|
||||
.map(|(it, substs)| (it, Some(substs)))?,
|
||||
}
|
||||
};
|
||||
Some((value, self_subst))
|
||||
}
|
||||
|
||||
fn add_required_obligations_for_value_path(&mut self, def: GenericDefId, subst: &Substitution) {
|
||||
let predicates = self.db.generic_predicates(def);
|
||||
for predicate in predicates.iter() {
|
||||
|
|
|
@ -113,7 +113,9 @@ pub struct TyLoweringContext<'a> {
|
|||
pub db: &'a dyn HirDatabase,
|
||||
resolver: &'a Resolver,
|
||||
in_binders: DebruijnIndex,
|
||||
owner: TypeOwnerId,
|
||||
// FIXME: Should not be an `Option` but `Resolver` currently does not return owners in all cases
|
||||
// where expected
|
||||
owner: Option<TypeOwnerId>,
|
||||
/// Note: Conceptually, it's thinkable that we could be in a location where
|
||||
/// some type params should be represented as placeholders, and others
|
||||
/// should be converted to variables. I think in practice, this isn't
|
||||
|
@ -127,6 +129,14 @@ pub struct TyLoweringContext<'a> {
|
|||
|
||||
impl<'a> TyLoweringContext<'a> {
|
||||
pub fn new(db: &'a dyn HirDatabase, resolver: &'a Resolver, owner: TypeOwnerId) -> Self {
|
||||
Self::new_maybe_unowned(db, resolver, Some(owner))
|
||||
}
|
||||
|
||||
pub fn new_maybe_unowned(
|
||||
db: &'a dyn HirDatabase,
|
||||
resolver: &'a Resolver,
|
||||
owner: Option<TypeOwnerId>,
|
||||
) -> Self {
|
||||
let impl_trait_mode = ImplTraitLoweringState::Disallowed;
|
||||
let type_param_mode = ParamLoweringMode::Placeholder;
|
||||
let in_binders = DebruijnIndex::INNERMOST;
|
||||
|
@ -213,10 +223,11 @@ impl<'a> TyLoweringContext<'a> {
|
|||
}
|
||||
|
||||
pub fn lower_const(&self, const_ref: &ConstRef, const_type: Ty) -> Const {
|
||||
let Some(owner) = self.owner else { return unknown_const(const_type) };
|
||||
const_or_path_to_chalk(
|
||||
self.db,
|
||||
self.resolver,
|
||||
self.owner,
|
||||
owner,
|
||||
const_type,
|
||||
const_ref,
|
||||
self.type_param_mode,
|
||||
|
@ -1768,10 +1779,11 @@ fn type_for_type_alias(db: &dyn HirDatabase, t: TypeAliasId) -> Binders<Ty> {
|
|||
let resolver = t.resolver(db.upcast());
|
||||
let ctx = TyLoweringContext::new(db, &resolver, t.into())
|
||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||
if db.type_alias_data(t).is_extern {
|
||||
let type_alias_data = db.type_alias_data(t);
|
||||
if type_alias_data.is_extern {
|
||||
Binders::empty(Interner, TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner))
|
||||
} else {
|
||||
let type_ref = &db.type_alias_data(t).type_ref;
|
||||
let type_ref = &type_alias_data.type_ref;
|
||||
let inner = ctx.lower_ty(type_ref.as_deref().unwrap_or(&TypeRef::Error));
|
||||
make_binders(db, &generics, inner)
|
||||
}
|
||||
|
@ -2042,7 +2054,7 @@ pub(crate) fn const_or_path_to_chalk(
|
|||
.intern_in_type_const(InTypeConstLoc {
|
||||
id: it,
|
||||
owner,
|
||||
thing: Box::new(InTypeConstIdMetadata(expected_ty.clone())),
|
||||
expected_ty: Box::new(InTypeConstIdMetadata(expected_ty.clone())),
|
||||
})
|
||||
.into();
|
||||
intern_const_scalar(
|
||||
|
|
|
@ -9,11 +9,10 @@ use super::visit_module;
|
|||
fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
|
||||
let (mut db, pos) = TestDB::with_position(
|
||||
"
|
||||
//- /lib.rs
|
||||
fn foo() -> i32 {
|
||||
$01 + 1
|
||||
}
|
||||
",
|
||||
//- /lib.rs
|
||||
fn foo() -> i32 {
|
||||
$01 + 1
|
||||
}",
|
||||
);
|
||||
{
|
||||
let events = db.log_executed(|| {
|
||||
|
@ -27,12 +26,11 @@ fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
|
|||
}
|
||||
|
||||
let new_text = "
|
||||
fn foo() -> i32 {
|
||||
1
|
||||
+
|
||||
1
|
||||
}
|
||||
";
|
||||
fn foo() -> i32 {
|
||||
1
|
||||
+
|
||||
1
|
||||
}";
|
||||
|
||||
db.set_file_text(pos.file_id, Arc::from(new_text));
|
||||
|
||||
|
@ -47,3 +45,55 @@ fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
|
|||
assert!(!format!("{events:?}").contains("infer"), "{events:#?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_inside_a_function_should_not_invalidate_types_in_another() {
|
||||
let (mut db, pos) = TestDB::with_position(
|
||||
"
|
||||
//- /lib.rs
|
||||
fn foo() -> f32 {
|
||||
1.0 + 2.0
|
||||
}
|
||||
fn bar() -> i32 {
|
||||
$01 + 1
|
||||
}
|
||||
fn baz() -> i32 {
|
||||
1 + 1
|
||||
}",
|
||||
);
|
||||
{
|
||||
let events = db.log_executed(|| {
|
||||
let module = db.module_for_file(pos.file_id);
|
||||
let crate_def_map = module.def_map(&db);
|
||||
visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
|
||||
db.infer(def);
|
||||
});
|
||||
});
|
||||
assert!(format!("{events:?}").contains("infer"))
|
||||
}
|
||||
|
||||
let new_text = "
|
||||
fn foo() -> f32 {
|
||||
1.0 + 2.0
|
||||
}
|
||||
fn bar() -> i32 {
|
||||
53
|
||||
}
|
||||
fn baz() -> i32 {
|
||||
1 + 1
|
||||
}
|
||||
";
|
||||
|
||||
db.set_file_text(pos.file_id, Arc::from(new_text));
|
||||
|
||||
{
|
||||
let events = db.log_executed(|| {
|
||||
let module = db.module_for_file(pos.file_id);
|
||||
let crate_def_map = module.def_map(&db);
|
||||
visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
|
||||
db.infer(def);
|
||||
});
|
||||
});
|
||||
assert!(format!("{events:?}").matches("infer").count() == 1, "{events:#?}")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1153,3 +1153,41 @@ fn main() {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_mismatch_pat_const_reference() {
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
const TEST_STR: &'static str = "abcd";
|
||||
|
||||
fn main() {
|
||||
let s = "abcd";
|
||||
match s {
|
||||
TEST_STR => (),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
struct Foo<T>(T);
|
||||
|
||||
impl<T> Foo<T> {
|
||||
const TEST_I32_REF: &'static i32 = &3;
|
||||
const TEST_I32: i32 = 3;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match &6 {
|
||||
Foo::<i32>::TEST_I32_REF => (),
|
||||
Foo::<i32>::TEST_I32 => (),
|
||||
//^^^^^^^^^^^^^^^^^^^^ expected &i32, got i32
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ use hir_def::{
|
|||
AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId,
|
||||
};
|
||||
use hir_expand::{
|
||||
db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo, InMacroFile, MacroCallId,
|
||||
MacroFileId, MacroFileIdExt,
|
||||
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
|
||||
InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
@ -29,7 +29,7 @@ use smallvec::{smallvec, SmallVec};
|
|||
use stdx::TupleExt;
|
||||
use syntax::{
|
||||
algo::skip_trivia_token,
|
||||
ast::{self, HasAttrs as _, HasDocComments, HasGenericParams, HasLoopBody, IsString as _},
|
||||
ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _},
|
||||
match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
|
||||
TextRange, TextSize,
|
||||
};
|
||||
|
@ -673,11 +673,22 @@ impl<'db> SemanticsImpl<'db> {
|
|||
}
|
||||
_ => 0,
|
||||
};
|
||||
// FIXME: here, the attribute's text range is used to strip away all
|
||||
// entries from the start of the attribute "list" up the the invoking
|
||||
// attribute. But in
|
||||
// ```
|
||||
// mod foo {
|
||||
// #![inner]
|
||||
// }
|
||||
// ```
|
||||
// we don't wanna strip away stuff in the `mod foo {` range, that is
|
||||
// here if the id corresponds to an inner attribute we got strip all
|
||||
// text ranges of the outer ones, and then all of the inner ones up
|
||||
// to the invoking attribute so that the inbetween is ignored.
|
||||
let text_range = item.syntax().text_range();
|
||||
let start = item
|
||||
.doc_comments_and_attrs()
|
||||
let start = collect_attrs(&item)
|
||||
.nth(attr_id)
|
||||
.map(|attr| match attr {
|
||||
.map(|attr| match attr.1 {
|
||||
Either::Left(it) => it.syntax().text_range().start(),
|
||||
Either::Right(it) => it.syntax().text_range().start(),
|
||||
})
|
||||
|
@ -937,10 +948,10 @@ impl<'db> SemanticsImpl<'db> {
|
|||
pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
|
||||
let analyze = self.analyze(ty.syntax())?;
|
||||
let ctx = LowerCtx::with_file_id(self.db.upcast(), analyze.file_id);
|
||||
let ty = hir_ty::TyLoweringContext::new(
|
||||
let ty = hir_ty::TyLoweringContext::new_maybe_unowned(
|
||||
self.db,
|
||||
&analyze.resolver,
|
||||
analyze.resolver.module().into(),
|
||||
analyze.resolver.type_owner(),
|
||||
)
|
||||
.lower_ty(&crate::TypeRef::from_ast(&ctx, ty.clone()));
|
||||
Some(Type::new_with_resolver(self.db, &analyze.resolver, ty))
|
||||
|
|
|
@ -1040,8 +1040,9 @@ fn resolve_hir_path_(
|
|||
let types = || {
|
||||
let (ty, unresolved) = match path.type_anchor() {
|
||||
Some(type_ref) => {
|
||||
let (_, res) = TyLoweringContext::new(db, resolver, resolver.module().into())
|
||||
.lower_ty_ext(type_ref);
|
||||
let (_, res) =
|
||||
TyLoweringContext::new_maybe_unowned(db, resolver, resolver.type_owner())
|
||||
.lower_ty_ext(type_ref);
|
||||
res.map(|ty_ns| (ty_ns, path.segments().first()))
|
||||
}
|
||||
None => {
|
||||
|
|
|
@ -23,6 +23,7 @@ pub struct FileSymbol {
|
|||
pub loc: DeclarationLocation,
|
||||
pub container_name: Option<SmolStr>,
|
||||
pub is_alias: bool,
|
||||
pub is_assoc: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -121,34 +122,34 @@ impl<'a> SymbolCollector<'a> {
|
|||
match module_def_id {
|
||||
ModuleDefId::ModuleId(id) => self.push_module(id),
|
||||
ModuleDefId::FunctionId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
self.collect_from_body(id);
|
||||
}
|
||||
ModuleDefId::AdtId(AdtId::StructId(id)) => self.push_decl(id),
|
||||
ModuleDefId::AdtId(AdtId::EnumId(id)) => self.push_decl(id),
|
||||
ModuleDefId::AdtId(AdtId::UnionId(id)) => self.push_decl(id),
|
||||
ModuleDefId::AdtId(AdtId::StructId(id)) => self.push_decl(id, false),
|
||||
ModuleDefId::AdtId(AdtId::EnumId(id)) => self.push_decl(id, false),
|
||||
ModuleDefId::AdtId(AdtId::UnionId(id)) => self.push_decl(id, false),
|
||||
ModuleDefId::ConstId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
self.collect_from_body(id);
|
||||
}
|
||||
ModuleDefId::StaticId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
self.collect_from_body(id);
|
||||
}
|
||||
ModuleDefId::TraitId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
self.collect_from_trait(id);
|
||||
}
|
||||
ModuleDefId::TraitAliasId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
}
|
||||
ModuleDefId::TypeAliasId(id) => {
|
||||
self.push_decl(id);
|
||||
self.push_decl(id, false);
|
||||
}
|
||||
ModuleDefId::MacroId(id) => match id {
|
||||
MacroId::Macro2Id(id) => self.push_decl(id),
|
||||
MacroId::MacroRulesId(id) => self.push_decl(id),
|
||||
MacroId::ProcMacroId(id) => self.push_decl(id),
|
||||
MacroId::Macro2Id(id) => self.push_decl(id, false),
|
||||
MacroId::MacroRulesId(id) => self.push_decl(id, false),
|
||||
MacroId::ProcMacroId(id) => self.push_decl(id, false),
|
||||
},
|
||||
// Don't index these.
|
||||
ModuleDefId::BuiltinType(_) => {}
|
||||
|
@ -190,6 +191,7 @@ impl<'a> SymbolCollector<'a> {
|
|||
container_name: self.current_container_name.clone(),
|
||||
loc: dec_loc,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -202,9 +204,9 @@ impl<'a> SymbolCollector<'a> {
|
|||
for &id in id {
|
||||
if id.module(self.db.upcast()) == module_id {
|
||||
match id {
|
||||
MacroId::Macro2Id(id) => self.push_decl(id),
|
||||
MacroId::MacroRulesId(id) => self.push_decl(id),
|
||||
MacroId::ProcMacroId(id) => self.push_decl(id),
|
||||
MacroId::Macro2Id(id) => self.push_decl(id, false),
|
||||
MacroId::MacroRulesId(id) => self.push_decl(id, false),
|
||||
MacroId::ProcMacroId(id) => self.push_decl(id, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,13 +268,13 @@ impl<'a> SymbolCollector<'a> {
|
|||
|
||||
fn push_assoc_item(&mut self, assoc_item_id: AssocItemId) {
|
||||
match assoc_item_id {
|
||||
AssocItemId::FunctionId(id) => self.push_decl(id),
|
||||
AssocItemId::ConstId(id) => self.push_decl(id),
|
||||
AssocItemId::TypeAliasId(id) => self.push_decl(id),
|
||||
AssocItemId::FunctionId(id) => self.push_decl(id, true),
|
||||
AssocItemId::ConstId(id) => self.push_decl(id, true),
|
||||
AssocItemId::TypeAliasId(id) => self.push_decl(id, true),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_decl<L>(&mut self, id: L)
|
||||
fn push_decl<L>(&mut self, id: L, is_assoc: bool)
|
||||
where
|
||||
L: Lookup + Into<ModuleDefId>,
|
||||
<L as Lookup>::Data: HasSource,
|
||||
|
@ -296,6 +298,7 @@ impl<'a> SymbolCollector<'a> {
|
|||
loc: dec_loc.clone(),
|
||||
container_name: self.current_container_name.clone(),
|
||||
is_alias: true,
|
||||
is_assoc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -306,6 +309,7 @@ impl<'a> SymbolCollector<'a> {
|
|||
container_name: self.current_container_name.clone(),
|
||||
loc: dec_loc,
|
||||
is_alias: false,
|
||||
is_assoc,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -331,6 +335,7 @@ impl<'a> SymbolCollector<'a> {
|
|||
loc: dec_loc.clone(),
|
||||
container_name: self.current_container_name.clone(),
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -341,6 +346,7 @@ impl<'a> SymbolCollector<'a> {
|
|||
container_name: self.current_container_name.clone(),
|
||||
loc: dec_loc,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -216,7 +216,7 @@ fn edit_field_references(
|
|||
edit.edit_file(file_id);
|
||||
for r in refs {
|
||||
if let Some(name_ref) = r.name.as_name_ref() {
|
||||
edit.replace(name_ref.syntax().text_range(), name.text());
|
||||
edit.replace(ctx.sema.original_range(name_ref.syntax()).range, name.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -458,13 +458,11 @@ impl Builder {
|
|||
}
|
||||
if let [import_edit] = &*self.imports_to_add {
|
||||
// snippets can have multiple imports, but normal completions only have up to one
|
||||
if let Some(original_path) = import_edit.original_path.as_ref() {
|
||||
label_detail.replace(SmolStr::from(format!(
|
||||
"{} (use {})",
|
||||
label_detail.as_deref().unwrap_or_default(),
|
||||
original_path.display(db)
|
||||
)));
|
||||
}
|
||||
label_detail.replace(SmolStr::from(format!(
|
||||
"{} (use {})",
|
||||
label_detail.as_deref().unwrap_or_default(),
|
||||
import_edit.import_path.display(db)
|
||||
)));
|
||||
} else if let Some(trait_name) = self.trait_name {
|
||||
label_detail.replace(SmolStr::from(format!(
|
||||
"{} (as {trait_name})",
|
||||
|
|
|
@ -181,7 +181,7 @@ fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option<V
|
|||
ctx.config.prefer_no_std,
|
||||
ctx.config.prefer_prelude,
|
||||
)?;
|
||||
Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item, None)))
|
||||
Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item)))
|
||||
};
|
||||
let mut res = Vec::with_capacity(requires.len());
|
||||
for import in requires {
|
||||
|
|
|
@ -597,8 +597,8 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
ct SPECIAL_CONST (use dep::test_mod::TestTrait) u8 DEPRECATED
|
||||
fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED
|
||||
ct SPECIAL_CONST (use dep::test_mod::TestTrait) u8 DEPRECATED
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -717,7 +717,7 @@ fn main() {
|
|||
check(
|
||||
fixture,
|
||||
expect![[r#"
|
||||
st Item (use foo::bar::baz::Item) Item
|
||||
st Item (use foo::bar) Item
|
||||
"#]],
|
||||
);
|
||||
|
||||
|
@ -725,19 +725,19 @@ fn main() {
|
|||
"Item",
|
||||
fixture,
|
||||
r#"
|
||||
use foo::bar;
|
||||
use foo::bar;
|
||||
|
||||
mod foo {
|
||||
pub mod bar {
|
||||
pub mod baz {
|
||||
pub struct Item;
|
||||
}
|
||||
}
|
||||
mod foo {
|
||||
pub mod bar {
|
||||
pub mod baz {
|
||||
pub struct Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
bar::baz::Item
|
||||
}"#,
|
||||
fn main() {
|
||||
bar::baz::Item
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -803,7 +803,7 @@ fn main() {
|
|||
check(
|
||||
fixture,
|
||||
expect![[r#"
|
||||
ct TEST_ASSOC (use foo::bar::Item) usize
|
||||
ct TEST_ASSOC (use foo::bar) usize
|
||||
"#]],
|
||||
);
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
//! Look up accessible paths for items.
|
||||
|
||||
use hir::{
|
||||
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef,
|
||||
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef, Name,
|
||||
PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use syntax::{
|
||||
ast::{self, make, HasName},
|
||||
utils::path_to_string_stripping_turbo_fish,
|
||||
AstNode, SyntaxNode,
|
||||
AstNode, SmolStr, SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -51,18 +51,11 @@ pub struct TraitImportCandidate {
|
|||
#[derive(Debug)]
|
||||
pub struct PathImportCandidate {
|
||||
/// Optional qualifier before name.
|
||||
pub qualifier: Option<FirstSegmentUnresolved>,
|
||||
pub qualifier: Option<Vec<SmolStr>>,
|
||||
/// The name the item (struct, trait, enum, etc.) should have.
|
||||
pub name: NameToImport,
|
||||
}
|
||||
|
||||
/// A qualifier that has a first segment and it's unresolved.
|
||||
#[derive(Debug)]
|
||||
pub struct FirstSegmentUnresolved {
|
||||
fist_segment: ast::NameRef,
|
||||
full_qualifier: ast::Path,
|
||||
}
|
||||
|
||||
/// A name that will be used during item lookups.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NameToImport {
|
||||
|
@ -195,18 +188,11 @@ pub struct LocatedImport {
|
|||
/// the original item is the associated constant, but the import has to be a trait that
|
||||
/// defines this constant.
|
||||
pub original_item: ItemInNs,
|
||||
/// A path of the original item.
|
||||
pub original_path: Option<ModPath>,
|
||||
}
|
||||
|
||||
impl LocatedImport {
|
||||
pub fn new(
|
||||
import_path: ModPath,
|
||||
item_to_import: ItemInNs,
|
||||
original_item: ItemInNs,
|
||||
original_path: Option<ModPath>,
|
||||
) -> Self {
|
||||
Self { import_path, item_to_import, original_item, original_path }
|
||||
pub fn new(import_path: ModPath, item_to_import: ItemInNs, original_item: ItemInNs) -> Self {
|
||||
Self { import_path, item_to_import, original_item }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,64 +337,75 @@ fn path_applicable_imports(
|
|||
)
|
||||
.filter_map(|item| {
|
||||
let mod_path = mod_path(item)?;
|
||||
Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(first_segment_unresolved) => {
|
||||
let unresolved_qualifier =
|
||||
path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
|
||||
let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
|
||||
items_locator::items_with_name(
|
||||
sema,
|
||||
current_crate,
|
||||
path_candidate.name.clone(),
|
||||
AssocSearchMode::Include,
|
||||
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
|
||||
)
|
||||
.filter_map(|item| {
|
||||
import_for_item(
|
||||
sema.db,
|
||||
mod_path,
|
||||
&unresolved_first_segment,
|
||||
&unresolved_qualifier,
|
||||
item,
|
||||
)
|
||||
Some(LocatedImport::new(mod_path, item, item))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(qualifier) => items_locator::items_with_name(
|
||||
sema,
|
||||
current_crate,
|
||||
path_candidate.name.clone(),
|
||||
AssocSearchMode::Include,
|
||||
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
|
||||
)
|
||||
.filter_map(|item| import_for_item(sema.db, mod_path, &qualifier, item))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn import_for_item(
|
||||
db: &RootDatabase,
|
||||
mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
|
||||
unresolved_first_segment: &str,
|
||||
unresolved_qualifier: &str,
|
||||
unresolved_qualifier: &[SmolStr],
|
||||
original_item: ItemInNs,
|
||||
) -> Option<LocatedImport> {
|
||||
let _p = profile::span("import_assets::import_for_item");
|
||||
let [first_segment, ..] = unresolved_qualifier else { return None };
|
||||
|
||||
let original_item_candidate = item_for_path_search(db, original_item)?;
|
||||
let import_path_candidate = mod_path(original_item_candidate)?;
|
||||
let import_path_string = import_path_candidate.display(db).to_string();
|
||||
let item_as_assoc = item_as_assoc(db, original_item);
|
||||
|
||||
let expected_import_end = if item_as_assoc(db, original_item).is_some() {
|
||||
unresolved_qualifier.to_string()
|
||||
} else {
|
||||
format!("{unresolved_qualifier}::{}", item_name(db, original_item)?.display(db))
|
||||
let (original_item_candidate, trait_item_to_import) = match item_as_assoc {
|
||||
Some(assoc_item) => match assoc_item.container(db) {
|
||||
AssocItemContainer::Trait(trait_) => {
|
||||
let trait_ = ItemInNs::from(ModuleDef::from(trait_));
|
||||
(trait_, Some(trait_))
|
||||
}
|
||||
AssocItemContainer::Impl(impl_) => {
|
||||
(ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)), None)
|
||||
}
|
||||
},
|
||||
None => (original_item, None),
|
||||
};
|
||||
if !import_path_string.contains(unresolved_first_segment)
|
||||
|| !import_path_string.ends_with(&expected_import_end)
|
||||
{
|
||||
let import_path_candidate = mod_path(original_item_candidate)?;
|
||||
|
||||
let mut import_path_candidate_segments = import_path_candidate.segments().iter().rev();
|
||||
let predicate = |it: EitherOrBoth<&SmolStr, &Name>| match it {
|
||||
// segments match, check next one
|
||||
EitherOrBoth::Both(a, b) if b.as_str() == Some(&**a) => None,
|
||||
// segments mismatch / qualifier is longer than the path, bail out
|
||||
EitherOrBoth::Both(..) | EitherOrBoth::Left(_) => Some(false),
|
||||
// all segments match and we have exhausted the qualifier, proceed
|
||||
EitherOrBoth::Right(_) => Some(true),
|
||||
};
|
||||
if item_as_assoc.is_none() {
|
||||
let item_name = item_name(db, original_item)?.as_text()?;
|
||||
let last_segment = import_path_candidate_segments.next()?;
|
||||
if last_segment.as_str() != Some(&*item_name) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let ends_with = unresolved_qualifier
|
||||
.iter()
|
||||
.rev()
|
||||
.zip_longest(import_path_candidate_segments)
|
||||
.find_map(predicate)
|
||||
.unwrap_or(true);
|
||||
if !ends_with {
|
||||
return None;
|
||||
}
|
||||
|
||||
let segment_import =
|
||||
find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?;
|
||||
let trait_item_to_import = item_as_assoc(db, original_item)
|
||||
.and_then(|assoc| assoc.containing_trait(db))
|
||||
.map(|trait_| ItemInNs::from(ModuleDef::from(trait_)));
|
||||
let segment_import = find_import_for_segment(db, original_item_candidate, first_segment)?;
|
||||
|
||||
Some(match (segment_import == original_item_candidate, trait_item_to_import) {
|
||||
(true, Some(_)) => {
|
||||
// FIXME we should be able to import both the trait and the segment,
|
||||
|
@ -416,42 +413,37 @@ fn import_for_item(
|
|||
// especially in case of lazy completion edit resolutions.
|
||||
return None;
|
||||
}
|
||||
(false, Some(trait_to_import)) => LocatedImport::new(
|
||||
mod_path(trait_to_import)?,
|
||||
trait_to_import,
|
||||
original_item,
|
||||
mod_path(original_item),
|
||||
),
|
||||
(true, None) => LocatedImport::new(
|
||||
import_path_candidate,
|
||||
original_item_candidate,
|
||||
original_item,
|
||||
mod_path(original_item),
|
||||
),
|
||||
(false, None) => LocatedImport::new(
|
||||
mod_path(segment_import)?,
|
||||
segment_import,
|
||||
original_item,
|
||||
mod_path(original_item),
|
||||
),
|
||||
(false, Some(trait_to_import)) => {
|
||||
LocatedImport::new(mod_path(trait_to_import)?, trait_to_import, original_item)
|
||||
}
|
||||
(true, None) => {
|
||||
LocatedImport::new(import_path_candidate, original_item_candidate, original_item)
|
||||
}
|
||||
(false, None) => {
|
||||
LocatedImport::new(mod_path(segment_import)?, segment_import, original_item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
|
||||
Some(match item {
|
||||
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
|
||||
Some(assoc_item) => match assoc_item.container(db) {
|
||||
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
|
||||
AssocItemContainer::Impl(impl_) => {
|
||||
ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
|
||||
}
|
||||
},
|
||||
Some(assoc_item) => item_for_path_search_assoc(db, assoc_item)?,
|
||||
None => item,
|
||||
},
|
||||
ItemInNs::Macros(_) => item,
|
||||
})
|
||||
}
|
||||
|
||||
fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Option<ItemInNs> {
|
||||
Some(match assoc_item.container(db) {
|
||||
AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)),
|
||||
AssocItemContainer::Impl(impl_) => {
|
||||
ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn find_import_for_segment(
|
||||
db: &RootDatabase,
|
||||
original_item: ItemInNs,
|
||||
|
@ -528,6 +520,7 @@ fn trait_applicable_items(
|
|||
.collect();
|
||||
|
||||
let mut located_imports = FxHashSet::default();
|
||||
let mut trait_import_paths = FxHashMap::default();
|
||||
|
||||
if trait_assoc_item {
|
||||
trait_candidate.receiver_ty.iterate_path_candidates(
|
||||
|
@ -545,12 +538,14 @@ fn trait_applicable_items(
|
|||
}
|
||||
let located_trait = assoc.containing_trait(db)?;
|
||||
let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
|
||||
let original_item = assoc_to_item(assoc);
|
||||
let import_path = trait_import_paths
|
||||
.entry(trait_item)
|
||||
.or_insert_with(|| mod_path(trait_item))
|
||||
.clone()?;
|
||||
located_imports.insert(LocatedImport::new(
|
||||
mod_path(trait_item)?,
|
||||
import_path,
|
||||
trait_item,
|
||||
original_item,
|
||||
mod_path(original_item),
|
||||
assoc_to_item(assoc),
|
||||
));
|
||||
}
|
||||
None::<()>
|
||||
|
@ -568,12 +563,14 @@ fn trait_applicable_items(
|
|||
if required_assoc_items.contains(&assoc) {
|
||||
let located_trait = assoc.containing_trait(db)?;
|
||||
let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
|
||||
let original_item = assoc_to_item(assoc);
|
||||
let import_path = trait_import_paths
|
||||
.entry(trait_item)
|
||||
.or_insert_with(|| mod_path(trait_item))
|
||||
.clone()?;
|
||||
located_imports.insert(LocatedImport::new(
|
||||
mod_path(trait_item)?,
|
||||
import_path,
|
||||
trait_item,
|
||||
original_item,
|
||||
mod_path(original_item),
|
||||
assoc_to_item(assoc),
|
||||
));
|
||||
}
|
||||
None::<()>
|
||||
|
@ -671,18 +668,13 @@ fn path_import_candidate(
|
|||
Some(match qualifier {
|
||||
Some(qualifier) => match sema.resolve_path(&qualifier) {
|
||||
None => {
|
||||
let qualifier_start =
|
||||
qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
|
||||
let qualifier_start_path =
|
||||
qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
|
||||
if sema.resolve_path(&qualifier_start_path).is_none() {
|
||||
ImportCandidate::Path(PathImportCandidate {
|
||||
qualifier: Some(FirstSegmentUnresolved {
|
||||
fist_segment: qualifier_start,
|
||||
full_qualifier: qualifier,
|
||||
}),
|
||||
name,
|
||||
})
|
||||
if qualifier.first_qualifier().map_or(true, |it| sema.resolve_path(&it).is_none()) {
|
||||
let mut qualifier = qualifier
|
||||
.segments_of_this_path_only_rev()
|
||||
.map(|seg| seg.name_ref().map(|name| SmolStr::new(name.text())))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
qualifier.reverse();
|
||||
ImportCandidate::Path(PathImportCandidate { qualifier: Some(qualifier), name })
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
//! The main reason for this module to exist is the fact that project's items and dependencies' items
|
||||
//! are located in different caches, with different APIs.
|
||||
use either::Either;
|
||||
use hir::{import_map, AsAssocItem, Crate, ItemInNs, Semantics};
|
||||
use hir::{import_map, Crate, ItemInNs, Semantics};
|
||||
use limit::Limit;
|
||||
|
||||
use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
|
||||
|
||||
/// A value to use, when uncertain which limit to pick.
|
||||
pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(40);
|
||||
pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(100);
|
||||
|
||||
pub use import_map::AssocSearchMode;
|
||||
|
||||
|
@ -36,7 +36,9 @@ pub fn items_with_name<'a>(
|
|||
NameToImport::Prefix(exact_name, case_sensitive)
|
||||
| NameToImport::Exact(exact_name, case_sensitive) => {
|
||||
let mut local_query = symbol_index::Query::new(exact_name.clone());
|
||||
let mut external_query = import_map::Query::new(exact_name);
|
||||
let mut external_query =
|
||||
// import_map::Query::new(exact_name).assoc_search_mode(assoc_item_search);
|
||||
import_map::Query::new(exact_name);
|
||||
if prefix {
|
||||
local_query.prefix();
|
||||
external_query = external_query.prefix();
|
||||
|
@ -101,8 +103,8 @@ fn find_items<'a>(
|
|||
.into_iter()
|
||||
.filter(move |candidate| match assoc_item_search {
|
||||
AssocSearchMode::Include => true,
|
||||
AssocSearchMode::Exclude => candidate.def.as_assoc_item(db).is_none(),
|
||||
AssocSearchMode::AssocItemsOnly => candidate.def.as_assoc_item(db).is_some(),
|
||||
AssocSearchMode::Exclude => !candidate.is_assoc,
|
||||
AssocSearchMode::AssocItemsOnly => candidate.is_assoc,
|
||||
})
|
||||
.map(|local_candidate| match local_candidate.def {
|
||||
hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
|
||||
|
|
|
@ -50,7 +50,7 @@ enum SearchMode {
|
|||
Prefix,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Query {
|
||||
query: String,
|
||||
lowercased: String,
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Struct",
|
||||
|
@ -65,6 +66,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "mul1",
|
||||
|
@ -94,6 +96,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "mul2",
|
||||
|
@ -123,6 +126,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "s1",
|
||||
|
@ -152,6 +156,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "s1",
|
||||
|
@ -181,6 +186,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "s2",
|
||||
|
@ -210,6 +216,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: true,
|
||||
is_assoc: false,
|
||||
},
|
||||
],
|
||||
),
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "CONST",
|
||||
|
@ -61,6 +62,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "CONST_WITH_INNER",
|
||||
|
@ -88,6 +90,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Enum",
|
||||
|
@ -117,6 +120,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "ItemLikeMacro",
|
||||
|
@ -146,6 +150,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Macro",
|
||||
|
@ -175,6 +180,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "STATIC",
|
||||
|
@ -202,6 +208,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Struct",
|
||||
|
@ -231,6 +238,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "StructFromMacro",
|
||||
|
@ -260,6 +268,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "StructInFn",
|
||||
|
@ -291,6 +300,7 @@
|
|||
"main",
|
||||
),
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "StructInNamedConst",
|
||||
|
@ -322,6 +332,7 @@
|
|||
"CONST_WITH_INNER",
|
||||
),
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "StructInUnnamedConst",
|
||||
|
@ -351,6 +362,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Trait",
|
||||
|
@ -378,6 +390,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Trait",
|
||||
|
@ -407,6 +420,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "Union",
|
||||
|
@ -436,6 +450,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "a_mod",
|
||||
|
@ -465,6 +480,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "b_mod",
|
||||
|
@ -494,6 +510,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "define_struct",
|
||||
|
@ -523,6 +540,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "impl_fn",
|
||||
|
@ -550,6 +568,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: true,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "macro_rules_macro",
|
||||
|
@ -579,6 +598,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "main",
|
||||
|
@ -606,6 +626,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "really_define_struct",
|
||||
|
@ -635,6 +656,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "trait_fn",
|
||||
|
@ -664,6 +686,7 @@
|
|||
"Trait",
|
||||
),
|
||||
is_alias: false,
|
||||
is_assoc: true,
|
||||
},
|
||||
],
|
||||
),
|
||||
|
@ -704,6 +727,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
],
|
||||
),
|
||||
|
@ -744,6 +768,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "StructInModB",
|
||||
|
@ -773,6 +798,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "SuperItemLikeMacro",
|
||||
|
@ -802,6 +828,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "ThisStruct",
|
||||
|
@ -831,6 +858,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
FileSymbol {
|
||||
name: "ThisStruct",
|
||||
|
@ -860,6 +888,7 @@
|
|||
},
|
||||
container_name: None,
|
||||
is_alias: false,
|
||||
is_assoc: false,
|
||||
},
|
||||
],
|
||||
),
|
||||
|
|
|
@ -44,21 +44,6 @@ extern crate core;
|
|||
extern crate self as foo;
|
||||
struct Foo;
|
||||
use foo::Foo as Bar;
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_panic_with_inner_attribute_in_presence_of_unresolved_crate() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
#[macro_use] extern crate doesnotexist;
|
||||
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unresolved extern crate
|
||||
mod _test_inner {
|
||||
#![empty_attr]
|
||||
//^^^^^^^^^^^^^^ error: unresolved macro `empty_attr`
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -67,6 +67,18 @@ macro_rules! m { () => {} } }
|
|||
|
||||
self::m!(); self::m2!();
|
||||
//^^ error: unresolved macro `self::m2!`
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_panic_with_inner_attribute_in_presence_of_unresolved_crate() {
|
||||
check_diagnostics(
|
||||
r#"
|
||||
mod _test_inner {
|
||||
#![empty_attr]
|
||||
//^^^^^^^^^^^^^^ error: unresolved macro `empty_attr`
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ use syntax::{
|
|||
};
|
||||
|
||||
// FIXME: Make this an enum
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum DiagnosticCode {
|
||||
RustcHardError(&'static str),
|
||||
RustcLint(&'static str),
|
||||
|
@ -198,7 +198,7 @@ impl Diagnostic {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
|
|
|
@ -422,6 +422,11 @@ fn ty_to_text_edit(
|
|||
Some(builder.finish())
|
||||
}
|
||||
|
||||
pub enum RangeLimit {
|
||||
Fixed(TextRange),
|
||||
NearestParent(TextSize),
|
||||
}
|
||||
|
||||
// Feature: Inlay Hints
|
||||
//
|
||||
// rust-analyzer shows additional information inline with the source code.
|
||||
|
@ -443,7 +448,7 @@ fn ty_to_text_edit(
|
|||
pub(crate) fn inlay_hints(
|
||||
db: &RootDatabase,
|
||||
file_id: FileId,
|
||||
range_limit: Option<TextRange>,
|
||||
range_limit: Option<RangeLimit>,
|
||||
config: &InlayHintsConfig,
|
||||
) -> Vec<InlayHint> {
|
||||
let _p = profile::span("inlay_hints");
|
||||
|
@ -458,13 +463,31 @@ pub(crate) fn inlay_hints(
|
|||
|
||||
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
|
||||
match range_limit {
|
||||
Some(range) => match file.covering_element(range) {
|
||||
Some(RangeLimit::Fixed(range)) => match file.covering_element(range) {
|
||||
NodeOrToken::Token(_) => return acc,
|
||||
NodeOrToken::Node(n) => n
|
||||
.descendants()
|
||||
.filter(|descendant| range.intersect(descendant.text_range()).is_some())
|
||||
.for_each(hints),
|
||||
},
|
||||
Some(RangeLimit::NearestParent(position)) => {
|
||||
match file.token_at_offset(position).left_biased() {
|
||||
Some(token) => {
|
||||
if let Some(parent_block) =
|
||||
token.parent_ancestors().find_map(ast::BlockExpr::cast)
|
||||
{
|
||||
parent_block.syntax().descendants().for_each(hints)
|
||||
} else if let Some(parent_item) =
|
||||
token.parent_ancestors().find_map(ast::Item::cast)
|
||||
{
|
||||
parent_item.syntax().descendants().for_each(hints)
|
||||
} else {
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
None => return acc,
|
||||
}
|
||||
}
|
||||
None => file.descendants().for_each(hints),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -177,7 +177,11 @@ mod tests {
|
|||
use syntax::{TextRange, TextSize};
|
||||
use test_utils::extract_annotations;
|
||||
|
||||
use crate::{fixture, inlay_hints::InlayHintsConfig, ClosureReturnTypeHints};
|
||||
use crate::{
|
||||
fixture,
|
||||
inlay_hints::{InlayHintsConfig, RangeLimit},
|
||||
ClosureReturnTypeHints,
|
||||
};
|
||||
|
||||
use crate::inlay_hints::tests::{
|
||||
check, check_edit, check_no_edit, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
|
||||
|
@ -400,7 +404,7 @@ fn main() {
|
|||
.inlay_hints(
|
||||
&InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
|
||||
file_id,
|
||||
Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
|
||||
Some(RangeLimit::Fixed(TextRange::new(TextSize::from(500), TextSize::from(600)))),
|
||||
)
|
||||
.unwrap();
|
||||
let actual =
|
||||
|
|
|
@ -94,7 +94,7 @@ pub use crate::{
|
|||
inlay_hints::{
|
||||
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,
|
||||
InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition,
|
||||
InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
|
||||
InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, RangeLimit,
|
||||
},
|
||||
join_lines::JoinLinesConfig,
|
||||
markup::Markup,
|
||||
|
@ -133,7 +133,9 @@ pub use ide_db::{
|
|||
symbol_index::Query,
|
||||
RootDatabase, SymbolKind,
|
||||
};
|
||||
pub use ide_diagnostics::{Diagnostic, DiagnosticsConfig, ExprFillDefaultMode, Severity};
|
||||
pub use ide_diagnostics::{
|
||||
Diagnostic, DiagnosticCode, DiagnosticsConfig, ExprFillDefaultMode, Severity,
|
||||
};
|
||||
pub use ide_ssr::SsrError;
|
||||
pub use syntax::{TextRange, TextSize};
|
||||
pub use text_edit::{Indel, TextEdit};
|
||||
|
@ -397,7 +399,7 @@ impl Analysis {
|
|||
&self,
|
||||
config: &InlayHintsConfig,
|
||||
file_id: FileId,
|
||||
range: Option<TextRange>,
|
||||
range: Option<RangeLimit>,
|
||||
) -> Cancellable<Vec<InlayHint>> {
|
||||
self.with_db(|db| inlay_hints::inlay_hints(db, file_id, range, config))
|
||||
}
|
||||
|
|
|
@ -311,7 +311,7 @@ where
|
|||
|
||||
let ident = tt::Leaf::from(tt::Ident {
|
||||
text: SmolStr::new(&token.to_text(conv)[1..]),
|
||||
span: conv.span_for(TextRange::at(
|
||||
span: conv.span_for(TextRange::new(
|
||||
abs_range.start() + TextSize::of('\''),
|
||||
abs_range.end(),
|
||||
)),
|
||||
|
@ -625,25 +625,6 @@ impl<SpanMap, S> Converter<SpanMap, S> {
|
|||
}
|
||||
|
||||
fn next_token(&mut self) -> Option<SyntaxToken> {
|
||||
// while let Some(ev) = self.preorder.next() {
|
||||
// match ev {
|
||||
// WalkEvent::Enter(SyntaxElement::Token(t)) => {
|
||||
// if let Some(leafs) = self.append.remove(&t.clone().into()) {
|
||||
// self.current_leafs.extend(leafs);
|
||||
// }
|
||||
// return Some(t);
|
||||
// }
|
||||
// WalkEvent::Enter(SyntaxElement::Node(n)) if self.remove.contains(&n) => {
|
||||
// self.preorder.skip_subtree();
|
||||
// if let Some(leafs) = self.append.remove(&n.into()) {
|
||||
// self.current_leafs.extend(leafs);
|
||||
// }
|
||||
// }
|
||||
// _ => (),
|
||||
// }
|
||||
// }
|
||||
// None;
|
||||
|
||||
while let Some(ev) = self.preorder.next() {
|
||||
match ev {
|
||||
WalkEvent::Enter(SyntaxElement::Token(t)) => return Some(t),
|
||||
|
|
|
@ -131,7 +131,6 @@ impl<'a, S: Span> TtIter<'a, S> {
|
|||
let buffer = tt::buffer::TokenBuffer::from_tokens(self.inner.as_slice());
|
||||
let parser_input = to_parser_input(&buffer);
|
||||
let tree_traversal = entry_point.parse(&parser_input);
|
||||
|
||||
let mut cursor = buffer.begin();
|
||||
let mut error = false;
|
||||
for step in tree_traversal.iter() {
|
||||
|
@ -163,12 +162,10 @@ impl<'a, S: Span> TtIter<'a, S> {
|
|||
let mut curr = buffer.begin();
|
||||
let mut res = vec![];
|
||||
|
||||
if cursor.is_root() {
|
||||
while curr != cursor {
|
||||
let Some(token) = curr.token_tree() else { break };
|
||||
res.push(token.cloned());
|
||||
curr = curr.bump();
|
||||
}
|
||||
while curr != cursor {
|
||||
let Some(token) = curr.token_tree() else { break };
|
||||
res.push(token.cloned());
|
||||
curr = curr.bump();
|
||||
}
|
||||
|
||||
self.inner = self.inner.as_slice()[res.len()..].iter();
|
||||
|
|
|
@ -131,7 +131,7 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result<String> {
|
|||
let len_bytes = &dot_rustc[8..16];
|
||||
let data_len = u64::from_le_bytes(len_bytes.try_into().unwrap()) as usize;
|
||||
(&dot_rustc[16..data_len + 12], 17)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
|
|
|
@ -42,6 +42,7 @@ tracing-tree.workspace = true
|
|||
triomphe.workspace = true
|
||||
nohash-hasher.workspace = true
|
||||
always-assert = "0.1.2"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
cfg.workspace = true
|
||||
flycheck.workspace = true
|
||||
|
|
|
@ -87,6 +87,7 @@ fn main() -> anyhow::Result<()> {
|
|||
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::RustcTests(cmd) => cmd.run()?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ mod ssr;
|
|||
mod lsif;
|
||||
mod scip;
|
||||
mod run_tests;
|
||||
mod rustc_tests;
|
||||
|
||||
mod progress_report;
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ xflags::xflags! {
|
|||
required path: PathBuf
|
||||
}
|
||||
|
||||
/// Run unit tests of the project using mir interpreter
|
||||
cmd rustc-tests {
|
||||
/// Directory with Cargo.toml.
|
||||
required rustc_repo: PathBuf
|
||||
|
||||
/// Only run tests with filter as substring
|
||||
optional --filter path: String
|
||||
}
|
||||
|
||||
cmd diagnostics {
|
||||
/// Directory with Cargo.toml.
|
||||
required path: PathBuf
|
||||
|
@ -159,6 +168,7 @@ pub enum RustAnalyzerCmd {
|
|||
Highlight(Highlight),
|
||||
AnalysisStats(AnalysisStats),
|
||||
RunTests(RunTests),
|
||||
RustcTests(RustcTests),
|
||||
Diagnostics(Diagnostics),
|
||||
Ssr(Ssr),
|
||||
Search(Search),
|
||||
|
@ -211,6 +221,12 @@ pub struct RunTests {
|
|||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RustcTests {
|
||||
pub rustc_repo: PathBuf,
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Diagnostics {
|
||||
pub path: PathBuf,
|
||||
|
|
236
crates/rust-analyzer/src/cli/rustc_tests.rs
Normal file
236
crates/rust-analyzer/src/cli/rustc_tests.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
|
||||
|
||||
use std::{
|
||||
cell::RefCell, collections::HashMap, fs::read_to_string, panic::AssertUnwindSafe, path::PathBuf,
|
||||
};
|
||||
|
||||
use hir::Crate;
|
||||
use ide::{AnalysisHost, Change, DiagnosticCode, DiagnosticsConfig};
|
||||
use profile::StopWatch;
|
||||
use project_model::{CargoConfig, ProjectWorkspace, RustLibSource, Sysroot};
|
||||
|
||||
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
|
||||
use triomphe::Arc;
|
||||
use vfs::{AbsPathBuf, FileId};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::cli::{flags, report_metric, Result};
|
||||
|
||||
struct Tester {
|
||||
host: AnalysisHost,
|
||||
root_file: FileId,
|
||||
pass_count: u64,
|
||||
ignore_count: u64,
|
||||
fail_count: u64,
|
||||
stopwatch: StopWatch,
|
||||
}
|
||||
|
||||
fn string_to_diagnostic_code_leaky(code: &str) -> DiagnosticCode {
|
||||
thread_local! {
|
||||
static LEAK_STORE: RefCell<HashMap<String, DiagnosticCode>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
LEAK_STORE.with_borrow_mut(|s| match s.get(code) {
|
||||
Some(c) => *c,
|
||||
None => {
|
||||
let v = DiagnosticCode::RustcHardError(format!("E{code}").leak());
|
||||
s.insert(code.to_owned(), v);
|
||||
v
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_errors_from_rustc_stderr_file(p: PathBuf) -> HashMap<DiagnosticCode, usize> {
|
||||
let text = read_to_string(p).unwrap();
|
||||
let mut result = HashMap::new();
|
||||
{
|
||||
let mut text = &*text;
|
||||
while let Some(p) = text.find("error[E") {
|
||||
text = &text[p + 7..];
|
||||
let code = string_to_diagnostic_code_leaky(&text[..4]);
|
||||
*result.entry(code).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
impl Tester {
|
||||
fn new() -> Result<Self> {
|
||||
let tmp_file = AbsPathBuf::assert("/tmp/ra-rustc-test.rs".into());
|
||||
std::fs::write(&tmp_file, "")?;
|
||||
let mut cargo_config = CargoConfig::default();
|
||||
cargo_config.sysroot = Some(RustLibSource::Discover);
|
||||
let workspace = ProjectWorkspace::DetachedFiles {
|
||||
files: vec![tmp_file.clone()],
|
||||
sysroot: Ok(
|
||||
Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env).unwrap()
|
||||
),
|
||||
rustc_cfg: vec![],
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: false,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
};
|
||||
let (host, _vfs, _proc_macro) =
|
||||
load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
|
||||
let db = host.raw_database();
|
||||
let krates = Crate::all(db);
|
||||
let root_crate = krates.iter().cloned().find(|krate| krate.origin(db).is_local()).unwrap();
|
||||
let root_file = root_crate.root_file(db);
|
||||
Ok(Self {
|
||||
host,
|
||||
root_file,
|
||||
pass_count: 0,
|
||||
ignore_count: 0,
|
||||
fail_count: 0,
|
||||
stopwatch: StopWatch::start(),
|
||||
})
|
||||
}
|
||||
|
||||
fn test(&mut self, p: PathBuf) {
|
||||
if p.parent().unwrap().file_name().unwrap() == "auxiliary" {
|
||||
// These are not tests
|
||||
return;
|
||||
}
|
||||
if IGNORED_TESTS.iter().any(|ig| p.file_name().is_some_and(|x| x == *ig)) {
|
||||
println!("{p:?} IGNORE");
|
||||
self.ignore_count += 1;
|
||||
return;
|
||||
}
|
||||
let stderr_path = p.with_extension("stderr");
|
||||
let expected = if stderr_path.exists() {
|
||||
detect_errors_from_rustc_stderr_file(stderr_path)
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let text = read_to_string(&p).unwrap();
|
||||
let mut change = Change::new();
|
||||
// Ignore unstable tests, since they move too fast and we do not intend to support all of them.
|
||||
let mut ignore_test = text.contains("#![feature");
|
||||
// Ignore test with extern crates, as this infra don't support them yet.
|
||||
ignore_test |= text.contains("// aux-build:") || text.contains("// aux-crate:");
|
||||
// Ignore test with extern modules similarly.
|
||||
ignore_test |= text.contains("mod ");
|
||||
// These should work, but they don't, and I don't know why, so ignore them.
|
||||
ignore_test |= text.contains("extern crate proc_macro");
|
||||
let should_have_no_error = text.contains("// check-pass")
|
||||
|| text.contains("// build-pass")
|
||||
|| text.contains("// run-pass");
|
||||
change.change_file(self.root_file, Some(Arc::from(text)));
|
||||
self.host.apply_change(change);
|
||||
let diagnostic_config = DiagnosticsConfig::test_sample();
|
||||
let diags = self
|
||||
.host
|
||||
.analysis()
|
||||
.diagnostics(&diagnostic_config, ide::AssistResolveStrategy::None, self.root_file)
|
||||
.unwrap();
|
||||
let mut actual = HashMap::new();
|
||||
for diag in diags {
|
||||
if !matches!(diag.code, DiagnosticCode::RustcHardError(_)) {
|
||||
continue;
|
||||
}
|
||||
if !should_have_no_error && !SUPPORTED_DIAGNOSTICS.contains(&diag.code) {
|
||||
continue;
|
||||
}
|
||||
*actual.entry(diag.code).or_insert(0) += 1;
|
||||
}
|
||||
// Ignore tests with diagnostics that we don't emit.
|
||||
ignore_test |= expected.keys().any(|k| !SUPPORTED_DIAGNOSTICS.contains(k));
|
||||
if ignore_test {
|
||||
println!("{p:?} IGNORE");
|
||||
self.ignore_count += 1;
|
||||
} else if actual == expected {
|
||||
println!("{p:?} PASS");
|
||||
self.pass_count += 1;
|
||||
} else {
|
||||
println!("{p:?} FAIL");
|
||||
println!("actual (r-a) = {:?}", actual);
|
||||
println!("expected (rustc) = {:?}", expected);
|
||||
self.fail_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn report(&mut self) {
|
||||
println!(
|
||||
"Pass count = {}, Fail count = {}, Ignore count = {}",
|
||||
self.pass_count, self.fail_count, self.ignore_count
|
||||
);
|
||||
println!("Testing time and memory = {}", self.stopwatch.elapsed());
|
||||
report_metric("rustc failed tests", self.fail_count, "#");
|
||||
report_metric("rustc testing time", self.stopwatch.elapsed().time.as_millis() as u64, "ms");
|
||||
}
|
||||
}
|
||||
|
||||
/// These tests break rust-analyzer (either by panicking or hanging) so we should ignore them.
|
||||
const IGNORED_TESTS: &[&str] = &[
|
||||
"trait-with-missing-associated-type-restriction.rs", // #15646
|
||||
"trait-with-missing-associated-type-restriction-fixable.rs", // #15646
|
||||
"resolve-self-in-impl.rs",
|
||||
"basic.rs", // ../rust/tests/ui/associated-type-bounds/return-type-notation/basic.rs
|
||||
"issue-26056.rs",
|
||||
"float-field.rs",
|
||||
"invalid_operator_trait.rs",
|
||||
"type-alias-impl-trait-assoc-dyn.rs",
|
||||
"deeply-nested_closures.rs", // exponential time
|
||||
"hang-on-deeply-nested-dyn.rs", // exponential time
|
||||
"dyn-rpit-and-let.rs", // unexpected free variable with depth `^1.0` with outer binder ^0
|
||||
"issue-16098.rs", // Huge recursion limit for macros?
|
||||
"issue-83471.rs", // crates/hir-ty/src/builder.rs:78:9: assertion failed: self.remaining() > 0
|
||||
];
|
||||
|
||||
const SUPPORTED_DIAGNOSTICS: &[DiagnosticCode] = &[
|
||||
DiagnosticCode::RustcHardError("E0023"),
|
||||
DiagnosticCode::RustcHardError("E0046"),
|
||||
DiagnosticCode::RustcHardError("E0063"),
|
||||
DiagnosticCode::RustcHardError("E0107"),
|
||||
DiagnosticCode::RustcHardError("E0117"),
|
||||
DiagnosticCode::RustcHardError("E0133"),
|
||||
DiagnosticCode::RustcHardError("E0210"),
|
||||
DiagnosticCode::RustcHardError("E0268"),
|
||||
DiagnosticCode::RustcHardError("E0308"),
|
||||
DiagnosticCode::RustcHardError("E0384"),
|
||||
DiagnosticCode::RustcHardError("E0407"),
|
||||
DiagnosticCode::RustcHardError("E0432"),
|
||||
DiagnosticCode::RustcHardError("E0451"),
|
||||
DiagnosticCode::RustcHardError("E0507"),
|
||||
DiagnosticCode::RustcHardError("E0583"),
|
||||
DiagnosticCode::RustcHardError("E0559"),
|
||||
DiagnosticCode::RustcHardError("E0616"),
|
||||
DiagnosticCode::RustcHardError("E0618"),
|
||||
DiagnosticCode::RustcHardError("E0624"),
|
||||
DiagnosticCode::RustcHardError("E0774"),
|
||||
DiagnosticCode::RustcHardError("E0767"),
|
||||
DiagnosticCode::RustcHardError("E0777"),
|
||||
];
|
||||
|
||||
impl flags::RustcTests {
|
||||
pub fn run(self) -> Result<()> {
|
||||
let mut tester = Tester::new()?;
|
||||
let walk_dir = WalkDir::new(self.rustc_repo.join("tests/ui"));
|
||||
for i in walk_dir {
|
||||
let i = i?;
|
||||
let p = i.into_path();
|
||||
if let Some(f) = &self.filter {
|
||||
if !p.as_os_str().to_string_lossy().contains(f) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if p.extension().map_or(true, |x| x != "rs") {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = std::panic::catch_unwind({
|
||||
let tester = AssertUnwindSafe(&mut tester);
|
||||
let p = p.clone();
|
||||
move || {
|
||||
let tester = tester;
|
||||
tester.0.test(p);
|
||||
}
|
||||
}) {
|
||||
println!("panic detected at test {:?}", p);
|
||||
std::panic::resume_unwind(e);
|
||||
}
|
||||
}
|
||||
tester.report();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1354,6 +1354,7 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: This should be an AbsolutePathBuf
|
||||
fn target_dir_from_config(&self) -> Option<PathBuf> {
|
||||
self.data.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir {
|
||||
TargetDirectory::UseSubdirectory(yes) if *yes => {
|
||||
|
|
|
@ -12,8 +12,8 @@ use anyhow::Context;
|
|||
|
||||
use ide::{
|
||||
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
|
||||
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
|
||||
Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
|
||||
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, RangeLimit,
|
||||
ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
|
||||
};
|
||||
use ide_db::SymbolKind;
|
||||
use lsp_server::ErrorCode;
|
||||
|
@ -1409,7 +1409,7 @@ pub(crate) fn handle_inlay_hints(
|
|||
let inlay_hints_config = snap.config.inlay_hints();
|
||||
Ok(Some(
|
||||
snap.analysis
|
||||
.inlay_hints(&inlay_hints_config, file_id, Some(range))?
|
||||
.inlay_hints(&inlay_hints_config, file_id, Some(RangeLimit::Fixed(range)))?
|
||||
.into_iter()
|
||||
.map(|it| {
|
||||
to_proto::inlay_hint(
|
||||
|
@ -1440,22 +1440,13 @@ pub(crate) fn handle_inlay_hints_resolve(
|
|||
anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
|
||||
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let range = from_proto::text_range(
|
||||
&line_index,
|
||||
lsp_types::Range { start: original_hint.position, end: original_hint.position },
|
||||
)?;
|
||||
let range_start = range.start();
|
||||
let range_end = range.end();
|
||||
let large_range = TextRange::new(
|
||||
range_start.checked_sub(1.into()).unwrap_or(range_start),
|
||||
range_end.checked_add(1.into()).unwrap_or(range_end),
|
||||
);
|
||||
let hint_position = from_proto::offset(&line_index, original_hint.position)?;
|
||||
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
|
||||
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
|
||||
let resolve_hints = snap.analysis.inlay_hints(
|
||||
&forced_resolve_inlay_hints_config,
|
||||
file_id,
|
||||
Some(large_range),
|
||||
Some(RangeLimit::NearestParent(hint_position)),
|
||||
)?;
|
||||
|
||||
let mut resolved_hints = resolve_hints
|
||||
|
|
|
@ -32,7 +32,10 @@ fn integrated_highlighting_benchmark() {
|
|||
let workspace_to_load = project_root();
|
||||
let file = "./crates/rust-analyzer/src/config.rs";
|
||||
|
||||
let cargo_config = CargoConfig::default();
|
||||
let cargo_config = CargoConfig {
|
||||
sysroot: Some(project_model::RustLibSource::Discover),
|
||||
..CargoConfig::default()
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::None,
|
||||
|
@ -85,7 +88,10 @@ fn integrated_completion_benchmark() {
|
|||
let workspace_to_load = project_root();
|
||||
let file = "./crates/hir/src/lib.rs";
|
||||
|
||||
let cargo_config = CargoConfig::default();
|
||||
let cargo_config = CargoConfig {
|
||||
sysroot: Some(project_model::RustLibSource::Discover),
|
||||
..CargoConfig::default()
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::None,
|
||||
|
@ -103,10 +109,46 @@ fn integrated_completion_benchmark() {
|
|||
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
|
||||
};
|
||||
|
||||
// kick off parsing and index population
|
||||
|
||||
let completion_offset = {
|
||||
let _it = stdx::timeit("change");
|
||||
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
|
||||
let completion_offset =
|
||||
patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
|
||||
+ "sel".len();
|
||||
let mut change = Change::new();
|
||||
change.change_file(file_id, Some(Arc::from(text)));
|
||||
host.apply_change(change);
|
||||
completion_offset
|
||||
};
|
||||
|
||||
{
|
||||
let _it = stdx::timeit("initial");
|
||||
let _span = profile::cpu_span();
|
||||
let analysis = host.analysis();
|
||||
analysis.highlight_as_html(file_id, false).unwrap();
|
||||
let config = CompletionConfig {
|
||||
enable_postfix_completions: true,
|
||||
enable_imports_on_the_fly: true,
|
||||
enable_self_on_the_fly: true,
|
||||
enable_private_editable: true,
|
||||
full_function_signatures: false,
|
||||
callable: Some(CallableSnippets::FillArguments),
|
||||
snippet_cap: SnippetCap::new(true),
|
||||
insert_use: InsertUseConfig {
|
||||
granularity: ImportGranularity::Crate,
|
||||
prefix_kind: hir::PrefixKind::ByCrate,
|
||||
enforce_granularity: true,
|
||||
group: true,
|
||||
skip_glob_imports: true,
|
||||
},
|
||||
snippets: Vec::new(),
|
||||
prefer_no_std: false,
|
||||
prefer_prelude: true,
|
||||
limit: None,
|
||||
};
|
||||
let position =
|
||||
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
|
||||
analysis.completions(&config, position, None).unwrap();
|
||||
}
|
||||
|
||||
profile::init_from("*>5");
|
||||
|
@ -116,8 +158,8 @@ fn integrated_completion_benchmark() {
|
|||
let _it = stdx::timeit("change");
|
||||
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
|
||||
let completion_offset =
|
||||
patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
|
||||
+ "sel".len();
|
||||
patch(&mut text, "sel;\ndb.struct_data(self.id)", ";sel;\ndb.struct_data(self.id)")
|
||||
+ ";sel".len();
|
||||
let mut change = Change::new();
|
||||
change.change_file(file_id, Some(Arc::from(text)));
|
||||
host.apply_change(change);
|
||||
|
|
|
@ -275,10 +275,19 @@ impl ast::Path {
|
|||
successors(Some(self.clone()), ast::Path::qualifier).last().unwrap()
|
||||
}
|
||||
|
||||
pub fn first_qualifier(&self) -> Option<ast::Path> {
|
||||
successors(self.qualifier(), ast::Path::qualifier).last()
|
||||
}
|
||||
|
||||
pub fn first_segment(&self) -> Option<ast::PathSegment> {
|
||||
self.first_qualifier_or_self().segment()
|
||||
}
|
||||
|
||||
// FIXME: Check usages of Self::segments, they might be wrong because of the logic of the bloew function
|
||||
pub fn segments_of_this_path_only_rev(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
|
||||
self.qualifiers_and_self().filter_map(|it| it.segment())
|
||||
}
|
||||
|
||||
pub fn segments(&self) -> impl Iterator<Item = ast::PathSegment> + Clone {
|
||||
successors(self.first_segment(), |p| {
|
||||
p.parent_path().parent_path().and_then(|p| p.segment())
|
||||
|
@ -289,6 +298,10 @@ impl ast::Path {
|
|||
successors(self.qualifier(), |p| p.qualifier())
|
||||
}
|
||||
|
||||
pub fn qualifiers_and_self(&self) -> impl Iterator<Item = ast::Path> + Clone {
|
||||
successors(Some(self.clone()), |p| p.qualifier())
|
||||
}
|
||||
|
||||
pub fn top_path(&self) -> ast::Path {
|
||||
let mut this = self.clone();
|
||||
while let Some(path) = this.parent_path() {
|
||||
|
|
|
@ -76,9 +76,6 @@ pub trait HasDocComments: HasAttrs {
|
|||
fn doc_comments(&self) -> DocCommentIter {
|
||||
DocCommentIter { iter: self.syntax().children_with_tokens() }
|
||||
}
|
||||
fn doc_comments_and_attrs(&self) -> AttrDocCommentIter {
|
||||
AttrDocCommentIter { iter: self.syntax().children_with_tokens() }
|
||||
}
|
||||
}
|
||||
|
||||
impl DocCommentIter {
|
||||
|
|
|
@ -1,48 +1,8 @@
|
|||
//! A set of utils methods to reuse on other abstraction levels
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{ast, match_ast, AstNode, SyntaxKind};
|
||||
|
||||
pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String {
|
||||
path.syntax()
|
||||
.children()
|
||||
.filter_map(|node| {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::PathSegment(it) => {
|
||||
Some(it.name_ref()?.to_string())
|
||||
},
|
||||
ast::Path(it) => {
|
||||
Some(path_to_string_stripping_turbo_fish(&it))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
})
|
||||
.join("::")
|
||||
}
|
||||
use crate::SyntaxKind;
|
||||
|
||||
pub fn is_raw_identifier(name: &str) -> bool {
|
||||
let is_keyword = SyntaxKind::from_keyword(name).is_some();
|
||||
is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::path_to_string_stripping_turbo_fish;
|
||||
use crate::ast::make;
|
||||
|
||||
#[test]
|
||||
fn turbofishes_are_stripped() {
|
||||
assert_eq!("Vec", path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>")),);
|
||||
assert_eq!(
|
||||
"Vec::new",
|
||||
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>::new")),
|
||||
);
|
||||
assert_eq!(
|
||||
"Vec::new",
|
||||
path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::new()")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,13 +61,17 @@ pub use paths::{AbsPath, AbsPathBuf};
|
|||
/// Most functions in rust-analyzer use this when they need to refer to a file.
|
||||
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
pub struct FileId(u32);
|
||||
// pub struct FileId(NonMaxU32);
|
||||
|
||||
impl FileId {
|
||||
/// Think twice about using this outside of tests. If this ends up in a wrong place it will cause panics!
|
||||
// FIXME: To be removed once we get rid of all `SpanData::DUMMY` usages.
|
||||
pub const BOGUS: FileId = FileId(0xe4e4e);
|
||||
pub const MAX_FILE_ID: u32 = 0x7fff_ffff;
|
||||
|
||||
#[inline]
|
||||
pub fn from_raw(raw: u32) -> FileId {
|
||||
pub const fn from_raw(raw: u32) -> FileId {
|
||||
assert!(raw <= Self::MAX_FILE_ID);
|
||||
FileId(raw)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "line-index"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
description = "Maps flat `TextSize` offsets to/from `(line, column)` representation."
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/line-index"
|
||||
|
|
|
@ -110,6 +110,7 @@ pub struct PublishReleaseNotes {
|
|||
#[derive(Debug)]
|
||||
pub enum MeasurementType {
|
||||
Build,
|
||||
RustcTests,
|
||||
AnalyzeSelf,
|
||||
AnalyzeRipgrep,
|
||||
AnalyzeWebRender,
|
||||
|
@ -122,6 +123,7 @@ impl FromStr for MeasurementType {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"build" => Ok(Self::Build),
|
||||
"rustc_tests" => Ok(Self::RustcTests),
|
||||
"self" => Ok(Self::AnalyzeSelf),
|
||||
"ripgrep-13.0.0" => Ok(Self::AnalyzeRipgrep),
|
||||
"webrender-2022" => Ok(Self::AnalyzeWebRender),
|
||||
|
@ -135,6 +137,7 @@ impl AsRef<str> for MeasurementType {
|
|||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::Build => "build",
|
||||
Self::RustcTests => "rustc_tests",
|
||||
Self::AnalyzeSelf => "self",
|
||||
Self::AnalyzeRipgrep => "ripgrep-13.0.0",
|
||||
Self::AnalyzeWebRender => "webrender-2022",
|
||||
|
|
|
@ -36,6 +36,9 @@ impl flags::Metrics {
|
|||
MeasurementType::Build => {
|
||||
metrics.measure_build(sh)?;
|
||||
}
|
||||
MeasurementType::RustcTests => {
|
||||
metrics.measure_rustc_tests(sh)?;
|
||||
}
|
||||
MeasurementType::AnalyzeSelf => {
|
||||
metrics.measure_analysis_stats_self(sh)?;
|
||||
}
|
||||
|
@ -50,6 +53,7 @@ impl flags::Metrics {
|
|||
}
|
||||
None => {
|
||||
metrics.measure_build(sh)?;
|
||||
metrics.measure_rustc_tests(sh)?;
|
||||
metrics.measure_analysis_stats_self(sh)?;
|
||||
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeRipgrep.as_ref())?;
|
||||
metrics.measure_analysis_stats(sh, MeasurementType::AnalyzeWebRender.as_ref())?;
|
||||
|
@ -78,6 +82,19 @@ impl Metrics {
|
|||
self.report("build", time.as_millis() as u64, "ms".into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn measure_rustc_tests(&mut self, sh: &Shell) -> anyhow::Result<()> {
|
||||
eprintln!("\nMeasuring rustc tests");
|
||||
|
||||
cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust").run()?;
|
||||
|
||||
let output = cmd!(sh, "./target/release/rust-analyzer rustc-tests ./rust").read()?;
|
||||
for (metric, value, unit) in parse_metrics(&output) {
|
||||
self.report(metric, value, unit.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn measure_analysis_stats_self(&mut self, sh: &Shell) -> anyhow::Result<()> {
|
||||
self.measure_analysis_stats_path(sh, "self", ".")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue