mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +00:00
Auto merge of #17898 - Veykril:descend-2.0, r=Veykril
internal: Improve macro token mapping heuristics Fixes https://github.com/rust-lang/rust-analyzer/issues/16235
This commit is contained in:
commit
a84c3d4d08
26 changed files with 489 additions and 421 deletions
|
@ -184,6 +184,8 @@ style = { level = "warn", priority = -1 }
|
|||
suspicious = { level = "warn", priority = -1 }
|
||||
|
||||
## allow following lints
|
||||
# subjective
|
||||
single_match = "allow"
|
||||
# () makes a fine error in most cases
|
||||
result_unit_err = "allow"
|
||||
# We don't expose public APIs that matter like this
|
||||
|
|
|
@ -461,3 +461,12 @@ impl<N: AstNode> InFile<N> {
|
|||
Some(InRealFile::new(file_id, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> InFile<T> {
|
||||
pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
|
||||
match self.file_id.repr() {
|
||||
HirFileIdRepr::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
|
||||
HirFileIdRepr::MacroFile(_) => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,6 +279,7 @@ pub enum MacroCallKind {
|
|||
}
|
||||
|
||||
pub trait HirFileIdExt {
|
||||
fn edition(self, db: &dyn ExpandDatabase) -> Edition;
|
||||
/// Returns the original file of this macro call hierarchy.
|
||||
fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId;
|
||||
|
||||
|
@ -293,6 +294,12 @@ pub trait HirFileIdExt {
|
|||
}
|
||||
|
||||
impl HirFileIdExt for HirFileId {
|
||||
fn edition(self, db: &dyn ExpandDatabase) -> Edition {
|
||||
match self.repr() {
|
||||
HirFileIdRepr::FileId(file_id) => file_id.edition(),
|
||||
HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition,
|
||||
}
|
||||
}
|
||||
fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId {
|
||||
let mut file_id = self;
|
||||
loop {
|
||||
|
|
|
@ -93,8 +93,7 @@ pub use crate::{
|
|||
diagnostics::*,
|
||||
has_source::HasSource,
|
||||
semantics::{
|
||||
DescendPreference, PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo,
|
||||
VisibleTraits,
|
||||
PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits,
|
||||
},
|
||||
};
|
||||
pub use hir_ty::method_resolution::TyFingerprint;
|
||||
|
|
|
@ -4,6 +4,7 @@ mod source_to_def;
|
|||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
convert::Infallible,
|
||||
fmt, iter, mem,
|
||||
ops::{self, ControlFlow, Not},
|
||||
};
|
||||
|
@ -48,11 +49,7 @@ use crate::{
|
|||
Variant, VariantDef,
|
||||
};
|
||||
|
||||
pub enum DescendPreference {
|
||||
SameText,
|
||||
SameKind,
|
||||
None,
|
||||
}
|
||||
const CONTINUE_NO_BREAKS: ControlFlow<Infallible, ()> = ControlFlow::Continue(());
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PathResolution {
|
||||
|
@ -182,6 +179,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
|||
|
||||
/// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
|
||||
/// descend it and find again
|
||||
// FIXME: Rethink this API
|
||||
pub fn find_node_at_offset_with_descend<N: AstNode>(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
|
@ -190,8 +188,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
|||
self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
|
||||
}
|
||||
|
||||
/// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
|
||||
/// Find an AstNode by offset inside SyntaxNode, if it is inside an attribute macro call,
|
||||
/// descend it and find again
|
||||
// FIXME: Rethink this API
|
||||
pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>(
|
||||
&'slf self,
|
||||
node: &SyntaxNode,
|
||||
|
@ -545,51 +544,53 @@ impl<'db> SemanticsImpl<'db> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Retrieves all the formatting parts of the format_args! template string.
|
||||
pub fn as_format_args_parts(
|
||||
&self,
|
||||
string: &ast::String,
|
||||
) -> Option<Vec<(TextRange, Option<PathResolution>)>> {
|
||||
if let Some(quote) = string.open_quote_text_range() {
|
||||
return self
|
||||
.descend_into_macros(DescendPreference::SameText, string.syntax().clone())
|
||||
.into_iter()
|
||||
.find_map(|token| {
|
||||
let string = ast::String::cast(token)?;
|
||||
let literal =
|
||||
string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
|
||||
let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
|
||||
let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
|
||||
let format_args = self.wrap_node_infile(format_args);
|
||||
let res = source_analyzer
|
||||
.as_format_args_parts(self.db, format_args.as_ref())?
|
||||
.map(|(range, res)| (range + quote.end(), res))
|
||||
.collect();
|
||||
Some(res)
|
||||
});
|
||||
}
|
||||
None
|
||||
let quote = string.open_quote_text_range()?;
|
||||
|
||||
let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
|
||||
self.descend_into_macros_breakable(token, |token| {
|
||||
(|| {
|
||||
let token = token.value;
|
||||
let string = ast::String::cast(token)?;
|
||||
let literal =
|
||||
string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
|
||||
let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
|
||||
let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
|
||||
let format_args = self.wrap_node_infile(format_args);
|
||||
let res = source_analyzer
|
||||
.as_format_args_parts(self.db, format_args.as_ref())?
|
||||
.map(|(range, res)| (range + quote.end(), res))
|
||||
.collect();
|
||||
Some(res)
|
||||
})()
|
||||
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves the formatting part of the format_args! template string at the given offset.
|
||||
pub fn check_for_format_args_template(
|
||||
&self,
|
||||
original_token: SyntaxToken,
|
||||
offset: TextSize,
|
||||
) -> Option<(TextRange, Option<PathResolution>)> {
|
||||
if let Some(original_string) = ast::String::cast(original_token.clone()) {
|
||||
if let Some(quote) = original_string.open_quote_text_range() {
|
||||
return self
|
||||
.descend_into_macros(DescendPreference::SameText, original_token)
|
||||
.into_iter()
|
||||
.find_map(|token| {
|
||||
self.resolve_offset_in_format_args(
|
||||
ast::String::cast(token)?,
|
||||
offset.checked_sub(quote.end())?,
|
||||
)
|
||||
})
|
||||
.map(|(range, res)| (range + quote.end(), res));
|
||||
}
|
||||
}
|
||||
None
|
||||
let original_string = ast::String::cast(original_token.clone())?;
|
||||
let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
|
||||
let quote = original_string.open_quote_text_range()?;
|
||||
self.descend_into_macros_breakable(original_token, |token| {
|
||||
(|| {
|
||||
let token = token.value;
|
||||
self.resolve_offset_in_format_args(
|
||||
ast::String::cast(token)?,
|
||||
offset.checked_sub(quote.end())?,
|
||||
)
|
||||
.map(|(range, res)| (range + quote.end(), res))
|
||||
})()
|
||||
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_offset_in_format_args(
|
||||
|
@ -619,30 +620,37 @@ impl<'db> SemanticsImpl<'db> {
|
|||
Some(it) => it,
|
||||
None => return res,
|
||||
};
|
||||
let file = self.find_file(node.syntax());
|
||||
let Some(file_id) = file.file_id.file_id() else {
|
||||
return res;
|
||||
};
|
||||
|
||||
if first == last {
|
||||
// node is just the token, so descend the token
|
||||
self.descend_into_macros_impl(first, &mut |InFile { value, .. }| {
|
||||
if let Some(node) = value
|
||||
.parent_ancestors()
|
||||
.take_while(|it| it.text_range() == value.text_range())
|
||||
.find_map(N::cast)
|
||||
{
|
||||
res.push(node)
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
self.descend_into_macros_impl(
|
||||
InRealFile::new(file_id, first),
|
||||
&mut |InFile { value, .. }| {
|
||||
if let Some(node) = value
|
||||
.parent_ancestors()
|
||||
.take_while(|it| it.text_range() == value.text_range())
|
||||
.find_map(N::cast)
|
||||
{
|
||||
res.push(node)
|
||||
}
|
||||
CONTINUE_NO_BREAKS
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Descend first and last token, then zip them to look for the node they belong to
|
||||
let mut scratch: SmallVec<[_; 1]> = smallvec![];
|
||||
self.descend_into_macros_impl(first, &mut |token| {
|
||||
self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| {
|
||||
scratch.push(token);
|
||||
ControlFlow::Continue(())
|
||||
CONTINUE_NO_BREAKS
|
||||
});
|
||||
|
||||
let mut scratch = scratch.into_iter();
|
||||
self.descend_into_macros_impl(
|
||||
last,
|
||||
InRealFile::new(file_id, last),
|
||||
&mut |InFile { value: last, file_id: last_fid }| {
|
||||
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
|
||||
if first_fid == last_fid {
|
||||
|
@ -659,130 +667,122 @@ impl<'db> SemanticsImpl<'db> {
|
|||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
CONTINUE_NO_BREAKS
|
||||
},
|
||||
);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
/// Descend the token into its macro call if it is part of one, returning the tokens in the
|
||||
/// expansion that it is associated with.
|
||||
pub fn descend_into_macros(
|
||||
pub fn descend_into_macros_cb(
|
||||
&self,
|
||||
mode: DescendPreference,
|
||||
token: SyntaxToken,
|
||||
) -> SmallVec<[SyntaxToken; 1]> {
|
||||
enum Dp<'t> {
|
||||
SameText(&'t str),
|
||||
SameKind(SyntaxKind),
|
||||
None,
|
||||
mut cb: impl FnMut(InFile<SyntaxToken>),
|
||||
) {
|
||||
if let Ok(token) = self.wrap_token_infile(token).into_real_file() {
|
||||
self.descend_into_macros_impl(token, &mut |t| {
|
||||
cb(t);
|
||||
CONTINUE_NO_BREAKS
|
||||
});
|
||||
}
|
||||
let fetch_kind = |token: &SyntaxToken| match token.parent() {
|
||||
Some(node) => match node.kind() {
|
||||
kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
|
||||
_ => token.kind(),
|
||||
},
|
||||
None => token.kind(),
|
||||
};
|
||||
let mode = match mode {
|
||||
DescendPreference::SameText => Dp::SameText(token.text()),
|
||||
DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
|
||||
DescendPreference::None => Dp::None,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
|
||||
let mut res = smallvec![];
|
||||
self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
|
||||
let is_a_match = match mode {
|
||||
Dp::SameText(text) => value.text() == text,
|
||||
Dp::SameKind(preferred_kind) => {
|
||||
let kind = fetch_kind(&value);
|
||||
kind == preferred_kind
|
||||
// special case for derive macros
|
||||
|| (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
|
||||
}
|
||||
Dp::None => true,
|
||||
};
|
||||
if is_a_match {
|
||||
res.push(value);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
|
||||
self.descend_into_macros_impl(token, &mut |t| {
|
||||
res.push(t.value);
|
||||
CONTINUE_NO_BREAKS
|
||||
});
|
||||
}
|
||||
if res.is_empty() {
|
||||
res.push(token);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn descend_into_macros_single(
|
||||
pub fn descend_into_macros_breakable<T>(
|
||||
&self,
|
||||
mode: DescendPreference,
|
||||
token: SyntaxToken,
|
||||
) -> SyntaxToken {
|
||||
enum Dp<'t> {
|
||||
SameText(&'t str),
|
||||
SameKind(SyntaxKind),
|
||||
None,
|
||||
}
|
||||
let fetch_kind = |token: &SyntaxToken| match token.parent() {
|
||||
Some(node) => match node.kind() {
|
||||
kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
|
||||
_ => token.kind(),
|
||||
},
|
||||
None => token.kind(),
|
||||
};
|
||||
let mode = match mode {
|
||||
DescendPreference::SameText => Dp::SameText(token.text()),
|
||||
DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
|
||||
DescendPreference::None => Dp::None,
|
||||
};
|
||||
let mut res = token.clone();
|
||||
self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
|
||||
let is_a_match = match mode {
|
||||
Dp::SameText(text) => value.text() == text,
|
||||
Dp::SameKind(preferred_kind) => {
|
||||
let kind = fetch_kind(&value);
|
||||
kind == preferred_kind
|
||||
// special case for derive macros
|
||||
|| (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
|
||||
}
|
||||
Dp::None => true,
|
||||
};
|
||||
res = value;
|
||||
if is_a_match {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
});
|
||||
res
|
||||
token: InRealFile<SyntaxToken>,
|
||||
mut cb: impl FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
|
||||
) -> Option<T> {
|
||||
self.descend_into_macros_impl(token.clone(), &mut cb)
|
||||
}
|
||||
|
||||
fn descend_into_macros_impl(
|
||||
/// Descends the token into expansions, returning the tokens that matches the input
|
||||
/// token's [`SyntaxKind`] and text.
|
||||
pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
|
||||
let mut r = smallvec![];
|
||||
let text = token.text();
|
||||
let kind = token.kind();
|
||||
|
||||
self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| {
|
||||
let mapped_kind = value.kind();
|
||||
let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
|
||||
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
|
||||
if matches {
|
||||
r.push(value);
|
||||
}
|
||||
});
|
||||
if r.is_empty() {
|
||||
r.push(token);
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// Descends the token into expansions, returning the first token that matches the input
|
||||
/// token's [`SyntaxKind`] and text.
|
||||
pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
|
||||
let text = token.text();
|
||||
let kind = token.kind();
|
||||
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
|
||||
self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| {
|
||||
let mapped_kind = value.kind();
|
||||
let any_ident_match =
|
||||
|| kind.is_any_identifier() && value.kind().is_any_identifier();
|
||||
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
|
||||
if matches {
|
||||
ControlFlow::Break(value)
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or(token)
|
||||
}
|
||||
|
||||
fn descend_into_macros_impl<T>(
|
||||
&self,
|
||||
token: SyntaxToken,
|
||||
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
|
||||
) {
|
||||
InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
|
||||
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
|
||||
) -> Option<T> {
|
||||
let _p = tracing::info_span!("descend_into_macros_impl").entered();
|
||||
let (sa, span, file_id) =
|
||||
match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
|
||||
Some(sa) => match sa.file_id.file_id() {
|
||||
Some(file_id) => (
|
||||
sa,
|
||||
self.db.real_span_map(file_id).span_for_range(token.text_range()),
|
||||
file_id.into(),
|
||||
),
|
||||
None => {
|
||||
stdx::never!();
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
let (sa, span, file_id) = token
|
||||
.parent()
|
||||
.and_then(|parent| {
|
||||
self.analyze_impl(InRealFile::new(file_id, &parent).into(), None, false)
|
||||
})
|
||||
.and_then(|sa| {
|
||||
let file_id = sa.file_id.file_id()?;
|
||||
Some((
|
||||
sa,
|
||||
self.db.real_span_map(file_id).span_for_range(token.text_range()),
|
||||
HirFileId::from(file_id),
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut m_cache = self.macro_call_cache.borrow_mut();
|
||||
let def_map = sa.resolver.def_map();
|
||||
|
||||
// A stack of tokens to process, along with the file they came from
|
||||
// These are tracked to know which macro calls we still have to look into
|
||||
// the tokens themselves aren't that interesting as the span that is being used to map
|
||||
// things down never changes.
|
||||
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
|
||||
|
||||
// Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
|
||||
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
|
||||
let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| {
|
||||
Some(
|
||||
|
@ -809,7 +809,13 @@ impl<'db> SemanticsImpl<'db> {
|
|||
res
|
||||
};
|
||||
|
||||
while let Some((file_id, mut tokens)) = stack.pop() {
|
||||
// Filters out all tokens that contain the given range (usually the macro call), any such
|
||||
// token is redundant as the corresponding macro call has already been processed
|
||||
let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| {
|
||||
tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range()))
|
||||
};
|
||||
|
||||
while let Some((expansion, ref mut tokens)) = stack.pop() {
|
||||
while let Some(token) = tokens.pop() {
|
||||
let was_not_remapped = (|| {
|
||||
// First expand into attribute invocations
|
||||
|
@ -817,7 +823,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
|
||||
// Don't force populate the dyn cache for items that don't have an attribute anyways
|
||||
item.attrs().next()?;
|
||||
Some((ctx.item_to_macro_call(InFile::new(file_id, &item))?, item))
|
||||
Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item))
|
||||
})
|
||||
});
|
||||
if let Some((call_id, item)) = containing_attribute_macro_call {
|
||||
|
@ -849,9 +855,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
})
|
||||
.unwrap_or_else(|| text_range.start());
|
||||
let text_range = TextRange::new(start, text_range.end());
|
||||
// remove any other token in this macro input, all their mappings are the
|
||||
// same as this one
|
||||
tokens.retain(|t| !text_range.contains_range(t.text_range()));
|
||||
filter_duplicates(tokens, text_range);
|
||||
return process_expansion_for_token(&mut stack, file_id);
|
||||
}
|
||||
|
||||
|
@ -862,6 +866,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
.map_while(Either::<ast::TokenTree, ast::Meta>::cast)
|
||||
.last()?;
|
||||
match tt {
|
||||
// function-like macro call
|
||||
Either::Left(tt) => {
|
||||
if tt.left_delimiter_token().map_or(false, |it| it == token) {
|
||||
return None;
|
||||
|
@ -870,7 +875,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
return None;
|
||||
}
|
||||
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
|
||||
let mcall = InFile::new(file_id, macro_call);
|
||||
let mcall = InFile::new(expansion, macro_call);
|
||||
let file_id = match m_cache.get(&mcall) {
|
||||
Some(&it) => it,
|
||||
None => {
|
||||
|
@ -888,9 +893,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
}
|
||||
};
|
||||
let text_range = tt.syntax().text_range();
|
||||
// remove any other token in this macro input, all their mappings are the
|
||||
// same as this one
|
||||
tokens.retain(|t| !text_range.contains_range(t.text_range()));
|
||||
filter_duplicates(tokens, text_range);
|
||||
|
||||
process_expansion_for_token(&mut stack, file_id).or(file_id
|
||||
.eager_arg(self.db.upcast())
|
||||
|
@ -899,6 +902,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
process_expansion_for_token(&mut stack, arg.as_macro_file())
|
||||
}))
|
||||
}
|
||||
// derive or derive helper
|
||||
Either::Right(meta) => {
|
||||
// attribute we failed expansion for earlier, this might be a derive invocation
|
||||
// or derive helper attribute
|
||||
|
@ -910,8 +914,8 @@ impl<'db> SemanticsImpl<'db> {
|
|||
// so try downmapping the token into the pseudo derive expansion
|
||||
// see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
|
||||
ctx.attr_to_derive_macro_call(
|
||||
InFile::new(file_id, &adt),
|
||||
InFile::new(file_id, attr.clone()),
|
||||
InFile::new(expansion, &adt),
|
||||
InFile::new(expansion, attr.clone()),
|
||||
)
|
||||
.map(|(_, call_id, _)| call_id)
|
||||
});
|
||||
|
@ -945,28 +949,29 @@ impl<'db> SemanticsImpl<'db> {
|
|||
)
|
||||
}
|
||||
}?;
|
||||
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
|
||||
if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &adt))) {
|
||||
return None;
|
||||
}
|
||||
let attr_name =
|
||||
attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
|
||||
// Not an attribute, nor a derive, so it's either a builtin or a derive helper
|
||||
// Not an attribute, nor a derive, so it's either an intert attribute or a derive helper
|
||||
// Try to resolve to a derive helper and downmap
|
||||
let id = self.db.ast_id_map(file_id).ast_id(&adt);
|
||||
let id = self.db.ast_id_map(expansion).ast_id(&adt);
|
||||
let helpers =
|
||||
def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
|
||||
def_map.derive_helpers_in_scope(InFile::new(expansion, id))?;
|
||||
|
||||
if !helpers.is_empty() {
|
||||
let text_range = attr.syntax().text_range();
|
||||
// remove any other token in this macro input, all their mappings are the
|
||||
// same as this
|
||||
tokens.retain(|t| !text_range.contains_range(t.text_range()));
|
||||
filter_duplicates(tokens, text_range);
|
||||
}
|
||||
|
||||
let mut res = None;
|
||||
for (.., derive) in
|
||||
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
|
||||
{
|
||||
// as there may be multiple derives registering the same helper
|
||||
// name, we gotta make sure to call this for all of them!
|
||||
// FIXME: We need to call `f` for all of them as well though!
|
||||
res = res.or(process_expansion_for_token(
|
||||
&mut stack,
|
||||
derive.as_macro_file(),
|
||||
|
@ -978,11 +983,14 @@ impl<'db> SemanticsImpl<'db> {
|
|||
})()
|
||||
.is_none();
|
||||
|
||||
if was_not_remapped && f(InFile::new(file_id, token)).is_break() {
|
||||
break;
|
||||
if was_not_remapped {
|
||||
if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) {
|
||||
return Some(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop
|
||||
|
@ -995,7 +1003,7 @@ impl<'db> SemanticsImpl<'db> {
|
|||
offset: TextSize,
|
||||
) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
|
||||
node.token_at_offset(offset)
|
||||
.map(move |token| self.descend_into_macros(DescendPreference::None, token))
|
||||
.map(move |token| self.descend_into_macros_exact(token))
|
||||
.map(|descendants| {
|
||||
descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
|
||||
})
|
||||
|
@ -1414,11 +1422,13 @@ impl<'db> SemanticsImpl<'db> {
|
|||
|
||||
/// Returns none if the file of the node is not part of a crate.
|
||||
fn analyze(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
|
||||
let node = self.find_file(node);
|
||||
self.analyze_impl(node, None, true)
|
||||
}
|
||||
|
||||
/// Returns none if the file of the node is not part of a crate.
|
||||
fn analyze_no_infer(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
|
||||
let node = self.find_file(node);
|
||||
self.analyze_impl(node, None, false)
|
||||
}
|
||||
|
||||
|
@ -1427,17 +1437,17 @@ impl<'db> SemanticsImpl<'db> {
|
|||
node: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
) -> Option<SourceAnalyzer> {
|
||||
let node = self.find_file(node);
|
||||
self.analyze_impl(node, Some(offset), false)
|
||||
}
|
||||
|
||||
fn analyze_impl(
|
||||
&self,
|
||||
node: &SyntaxNode,
|
||||
node: InFile<&SyntaxNode>,
|
||||
offset: Option<TextSize>,
|
||||
infer_body: bool,
|
||||
) -> Option<SourceAnalyzer> {
|
||||
let _p = tracing::info_span!("SemanticsImpl::analyze_impl").entered();
|
||||
let node = self.find_file(node);
|
||||
|
||||
let container = self.with_ctx(|ctx| ctx.find_container(node))?;
|
||||
|
||||
|
@ -1482,6 +1492,11 @@ impl<'db> SemanticsImpl<'db> {
|
|||
InFile::new(file_id, node)
|
||||
}
|
||||
|
||||
fn wrap_token_infile(&self, token: SyntaxToken) -> InFile<SyntaxToken> {
|
||||
let InFile { file_id, .. } = self.find_file(&token.parent().unwrap());
|
||||
InFile::new(file_id, token)
|
||||
}
|
||||
|
||||
/// Wraps the node in a [`InFile`] with the file id it belongs to.
|
||||
fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> {
|
||||
let root_node = find_root(node);
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
use crate::{utils, AssistContext, Assists};
|
||||
use hir::DescendPreference;
|
||||
use ide_db::{
|
||||
assists::{AssistId, AssistKind},
|
||||
syntax_helpers::{
|
||||
format_string::is_format_string,
|
||||
format_string_exprs::{parse_format_exprs, Arg},
|
||||
},
|
||||
syntax_helpers::format_string_exprs::{parse_format_exprs, Arg},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
|
@ -40,13 +36,7 @@ pub(crate) fn extract_expressions_from_format_string(
|
|||
let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
|
||||
let tt_delimiter = tt.left_delimiter_token()?.kind();
|
||||
|
||||
let expanded_t = ast::String::cast(
|
||||
ctx.sema
|
||||
.descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()),
|
||||
)?;
|
||||
if !is_format_string(&expanded_t) {
|
||||
return None;
|
||||
}
|
||||
let _ = ctx.sema.as_format_args_parts(&fmt_string)?;
|
||||
|
||||
let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
|
||||
if extracted_args.is_empty() {
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::{iter, ops::RangeInclusive};
|
|||
use ast::make;
|
||||
use either::Either;
|
||||
use hir::{
|
||||
DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef,
|
||||
PathResolution, Semantics, TypeInfo, TypeParam,
|
||||
HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
|
||||
TypeInfo, TypeParam,
|
||||
};
|
||||
use ide_db::{
|
||||
defs::{Definition, NameRefClass},
|
||||
|
@ -834,7 +834,7 @@ impl FunctionBody {
|
|||
.descendants_with_tokens()
|
||||
.filter_map(SyntaxElement::into_token)
|
||||
.filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self]))
|
||||
.flat_map(|t| sema.descend_into_macros(DescendPreference::None, t))
|
||||
.flat_map(|t| sema.descend_into_macros_exact(t))
|
||||
.for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use base_db::SourceRootDatabase;
|
||||
use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics};
|
||||
use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics};
|
||||
use span::{Edition, FileId};
|
||||
use syntax::{
|
||||
ast::{self, make},
|
||||
|
@ -112,11 +112,12 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool {
|
|||
!db.source_root(source_root_id).is_library
|
||||
}
|
||||
|
||||
// FIXME: This is a weird function
|
||||
pub fn get_definition(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: SyntaxToken,
|
||||
) -> Option<Definition> {
|
||||
for token in sema.descend_into_macros(DescendPreference::None, token) {
|
||||
for token in sema.descend_into_macros_exact(token) {
|
||||
let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
|
||||
if let Some(&[x]) = def.as_deref() {
|
||||
return Some(x);
|
||||
|
|
|
@ -9,8 +9,8 @@ use std::mem;
|
|||
|
||||
use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase};
|
||||
use hir::{
|
||||
sym, AsAssocItem, DefWithBody, DescendPreference, FileRange, HasAttrs, HasSource, HirFileIdExt,
|
||||
InFile, InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
|
||||
sym, AsAssocItem, DefWithBody, FileRange, HasAttrs, HasSource, HirFileIdExt, InFile,
|
||||
InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
|
||||
};
|
||||
use memchr::memmem::Finder;
|
||||
use parser::SyntaxKind;
|
||||
|
@ -549,9 +549,7 @@ impl<'a> FindUsages<'a> {
|
|||
// every textual hit. That function is notoriously
|
||||
// expensive even for things that do not get down mapped
|
||||
// into macros.
|
||||
sema.descend_into_macros(DescendPreference::None, token)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.parent())
|
||||
sema.descend_into_macros_exact(token).into_iter().filter_map(|it| it.parent())
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// FIXME Remove this, we have this information in the HIR now
|
||||
/// Parser for a format-like string. It is more allowing in terms of string contents,
|
||||
/// as we expect variable placeholders to be filled with expressions.
|
||||
///
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use std::iter;
|
||||
|
||||
use hir::{DescendPreference, Semantics};
|
||||
use hir::Semantics;
|
||||
use ide_db::{
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
helpers::pick_best_token,
|
||||
|
@ -86,7 +86,7 @@ pub(crate) fn outgoing_calls(
|
|||
})?;
|
||||
let mut calls = CallLocations::default();
|
||||
|
||||
sema.descend_into_macros(DescendPreference::None, token)
|
||||
sema.descend_into_macros_exact(token)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
|
||||
.filter_map(|item| match item {
|
||||
|
|
|
@ -10,10 +10,7 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions
|
|||
use stdx::format_to;
|
||||
use url::Url;
|
||||
|
||||
use hir::{
|
||||
db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference,
|
||||
HasAttrs,
|
||||
};
|
||||
use hir::{db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
|
||||
use ide_db::{
|
||||
base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase},
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
|
@ -144,7 +141,7 @@ pub(crate) fn external_docs(
|
|||
kind if kind.is_trivia() => 0,
|
||||
_ => 1,
|
||||
})?;
|
||||
let token = sema.descend_into_macros_single(DescendPreference::None, token);
|
||||
let token = sema.descend_into_macros_single_exact(token);
|
||||
|
||||
let node = token.parent()?;
|
||||
let definition = match_ast! {
|
||||
|
@ -289,7 +286,7 @@ impl DocCommentToken {
|
|||
let original_start = doc_token.text_range().start();
|
||||
let relative_comment_offset = offset - original_start - prefix_len;
|
||||
|
||||
sema.descend_into_macros(DescendPreference::None, doc_token).into_iter().find_map(|t| {
|
||||
sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
|
||||
let (node, descended_prefix_len) = match_ast! {
|
||||
match t {
|
||||
ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use hir::{DescendPreference, InFile, MacroFileIdExt, Semantics};
|
||||
use hir::{InFile, MacroFileIdExt, Semantics};
|
||||
use ide_db::{
|
||||
helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId,
|
||||
RootDatabase,
|
||||
|
@ -41,37 +41,30 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
|
|||
// struct Bar;
|
||||
// ```
|
||||
|
||||
let derive = sema
|
||||
.descend_into_macros(DescendPreference::None, tok.clone())
|
||||
.into_iter()
|
||||
.find_map(|descended| {
|
||||
let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
|
||||
if !macro_file.is_derive_attr_pseudo_expansion(db) {
|
||||
return None;
|
||||
}
|
||||
let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| {
|
||||
let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
|
||||
if !macro_file.is_derive_attr_pseudo_expansion(db) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
|
||||
// up map out of the #[derive] expansion
|
||||
let InFile { file_id, value: tokens } =
|
||||
hir::InMacroFile::new(macro_file, descended).upmap_once(db);
|
||||
let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
|
||||
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||
let expansions = sema.expand_derive_macro(&attr)?;
|
||||
let idx = attr
|
||||
.token_tree()?
|
||||
.token_trees_and_tokens()
|
||||
.filter_map(NodeOrToken::into_token)
|
||||
.take_while(|it| it != &token)
|
||||
.filter(|it| it.kind() == T![,])
|
||||
.count();
|
||||
let expansion = format(
|
||||
db,
|
||||
SyntaxKind::MACRO_ITEMS,
|
||||
position.file_id,
|
||||
expansions.get(idx).cloned()?,
|
||||
);
|
||||
Some(ExpandedMacro { name, expansion })
|
||||
});
|
||||
let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
|
||||
// up map out of the #[derive] expansion
|
||||
let InFile { file_id, value: tokens } =
|
||||
hir::InMacroFile::new(macro_file, descended).upmap_once(db);
|
||||
let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
|
||||
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||
let expansions = sema.expand_derive_macro(&attr)?;
|
||||
let idx = attr
|
||||
.token_tree()?
|
||||
.token_trees_and_tokens()
|
||||
.filter_map(NodeOrToken::into_token)
|
||||
.take_while(|it| it != &token)
|
||||
.filter(|it| it.kind() == T![,])
|
||||
.count();
|
||||
let expansion =
|
||||
format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
|
||||
Some(ExpandedMacro { name, expansion })
|
||||
});
|
||||
|
||||
if derive.is_some() {
|
||||
return derive;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::iter::successors;
|
||||
|
||||
use hir::{DescendPreference, Semantics};
|
||||
use hir::Semantics;
|
||||
use ide_db::RootDatabase;
|
||||
use syntax::{
|
||||
algo::{self, skip_trivia_token},
|
||||
|
@ -140,10 +140,8 @@ fn extend_tokens_from_range(
|
|||
|
||||
// compute original mapped token range
|
||||
let extended = {
|
||||
let fst_expanded =
|
||||
sema.descend_into_macros_single(DescendPreference::None, first_token.clone());
|
||||
let lst_expanded =
|
||||
sema.descend_into_macros_single(DescendPreference::None, last_token.clone());
|
||||
let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone());
|
||||
let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone());
|
||||
let mut lca =
|
||||
algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
|
||||
lca = shallowest_node(&lca);
|
||||
|
@ -157,7 +155,7 @@ fn extend_tokens_from_range(
|
|||
let validate = || {
|
||||
let extended = &extended;
|
||||
move |token: &SyntaxToken| -> bool {
|
||||
let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone());
|
||||
let expanded = sema.descend_into_macros_single_exact(token.clone());
|
||||
let parent = match expanded.parent() {
|
||||
Some(it) => it,
|
||||
None => return false,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use hir::{AsAssocItem, DescendPreference, Semantics};
|
||||
use hir::{AsAssocItem, Semantics};
|
||||
use ide_db::{
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
RootDatabase,
|
||||
|
@ -29,7 +29,7 @@ pub(crate) fn goto_declaration(
|
|||
.find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
|
||||
let range = original_token.text_range();
|
||||
let info: Vec<NavigationTarget> = sema
|
||||
.descend_into_macros(DescendPreference::None, original_token)
|
||||
.descend_into_macros(original_token)
|
||||
.iter()
|
||||
.filter_map(|token| {
|
||||
let parent = token.parent()?;
|
||||
|
|
|
@ -5,10 +5,7 @@ use crate::{
|
|||
navigation_target::{self, ToNav},
|
||||
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
|
||||
};
|
||||
use hir::{
|
||||
AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef,
|
||||
Semantics,
|
||||
};
|
||||
use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
|
||||
use ide_db::{
|
||||
base_db::{AnchoredPath, FileLoader, SourceDatabase},
|
||||
defs::{Definition, IdentClass},
|
||||
|
@ -86,7 +83,7 @@ pub(crate) fn goto_definition(
|
|||
}
|
||||
|
||||
let navs = sema
|
||||
.descend_into_macros(DescendPreference::None, original_token.clone())
|
||||
.descend_into_macros(original_token.clone())
|
||||
.into_iter()
|
||||
.filter_map(|token| {
|
||||
let parent = token.parent()?;
|
||||
|
@ -251,10 +248,7 @@ pub(crate) fn find_fn_or_blocks(
|
|||
None
|
||||
};
|
||||
|
||||
sema.descend_into_macros(DescendPreference::None, token.clone())
|
||||
.into_iter()
|
||||
.filter_map(find_ancestors)
|
||||
.collect_vec()
|
||||
sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec()
|
||||
}
|
||||
|
||||
fn nav_for_exit_points(
|
||||
|
@ -369,7 +363,7 @@ pub(crate) fn find_loops(
|
|||
None
|
||||
};
|
||||
|
||||
sema.descend_into_macros(DescendPreference::None, token.clone())
|
||||
sema.descend_into_macros(token.clone())
|
||||
.into_iter()
|
||||
.filter_map(find_ancestors)
|
||||
.collect_vec()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use hir::{AsAssocItem, DescendPreference, Impl, Semantics};
|
||||
use hir::{AsAssocItem, Impl, Semantics};
|
||||
use ide_db::{
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
helpers::pick_best_token,
|
||||
|
@ -10,7 +10,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
|
|||
|
||||
// Feature: Go to Implementation
|
||||
//
|
||||
// Navigates to the impl blocks of types.
|
||||
// Navigates to the impl items of types.
|
||||
//
|
||||
// |===
|
||||
// | Editor | Shortcut
|
||||
|
@ -32,48 +32,55 @@ pub(crate) fn goto_implementation(
|
|||
_ => 0,
|
||||
})?;
|
||||
let range = original_token.text_range();
|
||||
let navs =
|
||||
sema.descend_into_macros_single(DescendPreference::SameText, original_token)
|
||||
.parent()
|
||||
.and_then(ast::NameLike::cast)
|
||||
.and_then(|node| match &node {
|
||||
ast::NameLike::Name(name) => {
|
||||
NameClass::classify(&sema, name).and_then(|class| match class {
|
||||
NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
|
||||
NameClass::PatFieldShorthand { .. } => None,
|
||||
})
|
||||
}
|
||||
ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
|
||||
.and_then(|class| match class {
|
||||
NameRefClass::Definition(def) => Some(def),
|
||||
NameRefClass::FieldShorthand { .. }
|
||||
| NameRefClass::ExternCrateShorthand { .. } => None,
|
||||
}),
|
||||
ast::NameLike::Lifetime(_) => None,
|
||||
})
|
||||
.and_then(|def| {
|
||||
let navs = match def {
|
||||
Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
|
||||
Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
|
||||
Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
|
||||
Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)),
|
||||
Definition::Function(f) => {
|
||||
let assoc = f.as_assoc_item(sema.db)?;
|
||||
let name = assoc.name(sema.db)?;
|
||||
let trait_ = assoc.container_or_implemented_trait(sema.db)?;
|
||||
impls_for_trait_item(&sema, trait_, name)
|
||||
let navs = sema
|
||||
.descend_into_macros_exact(original_token)
|
||||
.iter()
|
||||
.filter_map(|token| {
|
||||
token
|
||||
.parent()
|
||||
.and_then(ast::NameLike::cast)
|
||||
.and_then(|node| match &node {
|
||||
ast::NameLike::Name(name) => {
|
||||
NameClass::classify(&sema, name).and_then(|class| match class {
|
||||
NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
|
||||
NameClass::PatFieldShorthand { .. } => None,
|
||||
})
|
||||
}
|
||||
Definition::Const(c) => {
|
||||
let assoc = c.as_assoc_item(sema.db)?;
|
||||
let name = assoc.name(sema.db)?;
|
||||
let trait_ = assoc.container_or_implemented_trait(sema.db)?;
|
||||
impls_for_trait_item(&sema, trait_, name)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(navs)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
|
||||
.and_then(|class| match class {
|
||||
NameRefClass::Definition(def) => Some(def),
|
||||
NameRefClass::FieldShorthand { .. }
|
||||
| NameRefClass::ExternCrateShorthand { .. } => None,
|
||||
}),
|
||||
ast::NameLike::Lifetime(_) => None,
|
||||
})
|
||||
.and_then(|def| {
|
||||
let navs = match def {
|
||||
Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
|
||||
Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
|
||||
Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
|
||||
Definition::BuiltinType(builtin) => {
|
||||
impls_for_ty(&sema, builtin.ty(sema.db))
|
||||
}
|
||||
Definition::Function(f) => {
|
||||
let assoc = f.as_assoc_item(sema.db)?;
|
||||
let name = assoc.name(sema.db)?;
|
||||
let trait_ = assoc.container_or_implemented_trait(sema.db)?;
|
||||
impls_for_trait_item(&sema, trait_, name)
|
||||
}
|
||||
Definition::Const(c) => {
|
||||
let assoc = c.as_assoc_item(sema.db)?;
|
||||
let name = assoc.name(sema.db)?;
|
||||
let trait_ = assoc.container_or_implemented_trait(sema.db)?;
|
||||
impls_for_trait_item(&sema, trait_, name)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(navs)
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Some(RangeInfo { range, info: navs })
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use hir::{DescendPreference, GenericParam};
|
||||
use hir::GenericParam;
|
||||
use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
|
||||
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
|
||||
|
||||
|
@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition(
|
|||
}
|
||||
|
||||
let range = token.text_range();
|
||||
sema.descend_into_macros(DescendPreference::None, token)
|
||||
sema.descend_into_macros(token)
|
||||
.into_iter()
|
||||
.filter_map(|token| {
|
||||
let ty = sema
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::iter;
|
||||
|
||||
use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics};
|
||||
use hir::{db, FilePosition, FileRange, HirFileId, InFile, Semantics};
|
||||
use ide_db::{
|
||||
defs::{Definition, IdentClass},
|
||||
helpers::pick_best_token,
|
||||
|
@ -542,7 +542,7 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange
|
|||
}
|
||||
|
||||
fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
|
||||
sema.descend_into_macros(DescendPreference::None, token)
|
||||
sema.descend_into_macros_exact(token)
|
||||
.into_iter()
|
||||
.filter_map(|token| IdentClass::classify_token(sema, &token))
|
||||
.flat_map(IdentClass::definitions_no_ops)
|
||||
|
|
|
@ -6,7 +6,7 @@ mod tests;
|
|||
use std::{iter, ops::Not};
|
||||
|
||||
use either::Either;
|
||||
use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics};
|
||||
use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics};
|
||||
use ide_db::{
|
||||
defs::{Definition, IdentClass, NameRefClass, OperatorClass},
|
||||
famous_defs::FamousDefs,
|
||||
|
@ -58,7 +58,7 @@ pub enum HoverDocFormat {
|
|||
PlainText,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum HoverAction {
|
||||
Runnable(Runnable),
|
||||
Implementation(FilePosition),
|
||||
|
@ -97,7 +97,7 @@ pub struct HoverGotoTypeData {
|
|||
}
|
||||
|
||||
/// Contains the results when hovering over an item
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub struct HoverResult {
|
||||
pub markup: Markup,
|
||||
pub actions: Vec<HoverAction>,
|
||||
|
@ -119,7 +119,7 @@ pub(crate) fn hover(
|
|||
let edition =
|
||||
sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
|
||||
let mut res = if range.is_empty() {
|
||||
hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config, edition)
|
||||
hover_offset(sema, FilePosition { file_id, offset: range.start() }, file, config, edition)
|
||||
} else {
|
||||
hover_ranged(sema, frange, file, config, edition)
|
||||
}?;
|
||||
|
@ -131,7 +131,7 @@ pub(crate) fn hover(
|
|||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn hover_simple(
|
||||
fn hover_offset(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
FilePosition { file_id, offset }: FilePosition,
|
||||
file: SyntaxNode,
|
||||
|
@ -178,34 +178,43 @@ fn hover_simple(
|
|||
return Some(RangeInfo::new(range, res));
|
||||
}
|
||||
|
||||
let in_attr = original_token
|
||||
.parent_ancestors()
|
||||
.filter_map(ast::Item::cast)
|
||||
.any(|item| sema.is_attr_macro_call(&item))
|
||||
&& !matches!(
|
||||
original_token.parent().and_then(ast::TokenTree::cast),
|
||||
Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
|
||||
);
|
||||
|
||||
// prefer descending the same token kind in attribute expansions, in normal macros text
|
||||
// equivalency is more important
|
||||
let descended = sema.descend_into_macros(
|
||||
if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText },
|
||||
original_token.clone(),
|
||||
);
|
||||
let descended = || descended.iter();
|
||||
let mut descended = sema.descend_into_macros(original_token.clone());
|
||||
|
||||
let result = descended()
|
||||
// try lint hover
|
||||
.find_map(|token| {
|
||||
let kind = original_token.kind();
|
||||
let text = original_token.text();
|
||||
let ident_kind = kind.is_any_identifier();
|
||||
|
||||
descended.sort_by_cached_key(|tok| {
|
||||
let tok_kind = tok.kind();
|
||||
|
||||
let exact_same_kind = tok_kind == kind;
|
||||
let both_idents = exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
|
||||
let same_text = tok.text() == text;
|
||||
// anything that mapped into a token tree has likely no semantic information
|
||||
let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
|
||||
!((both_idents as usize)
|
||||
| ((exact_same_kind as usize) << 1)
|
||||
| ((same_text as usize) << 2)
|
||||
| ((no_tt_parent as usize) << 3))
|
||||
});
|
||||
|
||||
let mut res = vec![];
|
||||
for token in descended {
|
||||
let is_same_kind = token.kind() == kind;
|
||||
let lint_hover = (|| {
|
||||
// FIXME: Definition should include known lints and the like instead of having this special case here
|
||||
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||
render::try_for_lint(&attr, token)
|
||||
})
|
||||
// try definitions
|
||||
.or_else(|| {
|
||||
descended()
|
||||
.filter_map(|token| {
|
||||
render::try_for_lint(&attr, &token)
|
||||
})();
|
||||
if let Some(lint_hover) = lint_hover {
|
||||
res.push(lint_hover);
|
||||
continue;
|
||||
}
|
||||
let definitions = (|| {
|
||||
Some(
|
||||
'a: {
|
||||
let node = token.parent()?;
|
||||
|
||||
// special case macro calls, we wanna render the invoked arm index
|
||||
|
@ -220,11 +229,11 @@ fn hover_simple(
|
|||
.and_then(ast::MacroCall::cast)
|
||||
{
|
||||
if let Some(macro_) = sema.resolve_macro_call(¯o_call) {
|
||||
return Some(vec![(
|
||||
break 'a vec![(
|
||||
Definition::Macro(macro_),
|
||||
sema.resolve_macro_call_arm(¯o_call),
|
||||
node,
|
||||
)]);
|
||||
)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,88 +242,101 @@ fn hover_simple(
|
|||
match IdentClass::classify_node(sema, &node)? {
|
||||
// It's better for us to fall back to the keyword hover here,
|
||||
// rendering poll is very confusing
|
||||
IdentClass::Operator(OperatorClass::Await(_)) => None,
|
||||
IdentClass::Operator(OperatorClass::Await(_)) => return None,
|
||||
|
||||
IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
|
||||
decl,
|
||||
..
|
||||
}) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
|
||||
}) => {
|
||||
vec![(Definition::ExternCrateDecl(decl), None, node)]
|
||||
}
|
||||
|
||||
class => Some(
|
||||
class => {
|
||||
multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
.into_iter()
|
||||
.unique_by(|&(def, _, _)| def)
|
||||
.map(|(def, macro_arm, node)| {
|
||||
hover_for_definition(sema, file_id, def, &node, macro_arm, config, edition)
|
||||
})
|
||||
.reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
|
||||
acc.actions.extend(actions);
|
||||
acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
|
||||
acc
|
||||
})
|
||||
})
|
||||
// try keywords
|
||||
.or_else(|| descended().find_map(|token| render::keyword(sema, config, token, edition)))
|
||||
// try _ hovers
|
||||
.or_else(|| descended().find_map(|token| render::underscore(sema, config, token, edition)))
|
||||
// try rest pattern hover
|
||||
.or_else(|| {
|
||||
descended().find_map(|token| {
|
||||
if token.kind() != DOT2 {
|
||||
return None;
|
||||
}
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})();
|
||||
if let Some(definitions) = definitions {
|
||||
res.extend(definitions);
|
||||
continue;
|
||||
}
|
||||
let keywords = || render::keyword(sema, config, &token, edition);
|
||||
let underscore = || {
|
||||
if !is_same_kind {
|
||||
return None;
|
||||
}
|
||||
render::underscore(sema, config, &token, edition)
|
||||
};
|
||||
let rest_pat = || {
|
||||
if !is_same_kind || token.kind() != DOT2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
|
||||
let record_pat_field_list =
|
||||
rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
|
||||
let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
|
||||
let record_pat_field_list =
|
||||
rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
|
||||
|
||||
let record_pat =
|
||||
record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
|
||||
let record_pat =
|
||||
record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
|
||||
|
||||
Some(render::struct_rest_pat(sema, config, &record_pat, edition))
|
||||
})
|
||||
})
|
||||
// try () call hovers
|
||||
.or_else(|| {
|
||||
descended().find_map(|token| {
|
||||
if token.kind() != T!['('] && token.kind() != T![')'] {
|
||||
return None;
|
||||
Some(render::struct_rest_pat(sema, config, &record_pat, edition))
|
||||
};
|
||||
let call = || {
|
||||
if !is_same_kind || token.kind() != T!['('] && token.kind() != T![')'] {
|
||||
return None;
|
||||
}
|
||||
let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
|
||||
let call_expr = syntax::match_ast! {
|
||||
match arg_list {
|
||||
ast::CallExpr(expr) => expr.into(),
|
||||
ast::MethodCallExpr(expr) => expr.into(),
|
||||
_ => return None,
|
||||
}
|
||||
let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
|
||||
let call_expr = syntax::match_ast! {
|
||||
match arg_list {
|
||||
ast::CallExpr(expr) => expr.into(),
|
||||
ast::MethodCallExpr(expr) => expr.into(),
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
render::type_info_of(sema, config, &Either::Left(call_expr), edition)
|
||||
})
|
||||
})
|
||||
// try closure
|
||||
.or_else(|| {
|
||||
descended().find_map(|token| {
|
||||
if token.kind() != T![|] {
|
||||
return None;
|
||||
}
|
||||
let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
|
||||
render::closure_expr(sema, config, c, edition)
|
||||
})
|
||||
})
|
||||
// tokens
|
||||
.or_else(|| {
|
||||
};
|
||||
render::type_info_of(sema, config, &Either::Left(call_expr), edition)
|
||||
};
|
||||
let closure = || {
|
||||
if !is_same_kind || token.kind() != T![|] {
|
||||
return None;
|
||||
}
|
||||
let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
|
||||
render::closure_expr(sema, config, c, edition)
|
||||
};
|
||||
let literal = || {
|
||||
render::literal(sema, original_token.clone(), edition)
|
||||
.map(|markup| HoverResult { markup, actions: vec![] })
|
||||
});
|
||||
};
|
||||
if let Some(result) = keywords()
|
||||
.or_else(underscore)
|
||||
.or_else(rest_pat)
|
||||
.or_else(call)
|
||||
.or_else(closure)
|
||||
.or_else(literal)
|
||||
{
|
||||
res.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|mut res: HoverResult| {
|
||||
res.actions = dedupe_or_merge_hover_actions(res.actions);
|
||||
RangeInfo::new(original_token.text_range(), res)
|
||||
})
|
||||
res.into_iter()
|
||||
.unique()
|
||||
.reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
|
||||
acc.actions.extend(actions);
|
||||
acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
|
||||
acc
|
||||
})
|
||||
.map(|mut res: HoverResult| {
|
||||
res.actions = dedupe_or_merge_hover_actions(res.actions);
|
||||
RangeInfo::new(original_token.text_range(), res)
|
||||
})
|
||||
}
|
||||
|
||||
fn hover_ranged(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! what is used by LSP, so let's keep it simple.
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Markup {
|
||||
text: String,
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use core::fmt;
|
||||
|
||||
use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, DescendPreference, MacroKind, Semantics};
|
||||
use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics};
|
||||
use ide_db::{
|
||||
base_db::{CrateOrigin, LangCrateOrigin},
|
||||
defs::{Definition, IdentClass},
|
||||
|
@ -154,7 +154,7 @@ pub(crate) fn moniker(
|
|||
});
|
||||
}
|
||||
let navs = sema
|
||||
.descend_into_macros(DescendPreference::None, original_token.clone())
|
||||
.descend_into_macros_exact(original_token.clone())
|
||||
.into_iter()
|
||||
.filter_map(|token| {
|
||||
IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//! at the index that the match starts at and its tree parent is
|
||||
//! resolved to the search element definition, we get a reference.
|
||||
|
||||
use hir::{DescendPreference, PathResolution, Semantics};
|
||||
use hir::{PathResolution, Semantics};
|
||||
use ide_db::{
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
search::{ReferenceCategory, SearchScope, UsageSearchResult},
|
||||
|
@ -149,7 +149,7 @@ pub(crate) fn find_defs<'a>(
|
|||
}
|
||||
|
||||
Some(
|
||||
sema.descend_into_macros(DescendPreference::SameText, token)
|
||||
sema.descend_into_macros_exact(token)
|
||||
.into_iter()
|
||||
.filter_map(|it| ast::NameLike::cast(it.parent()?))
|
||||
.filter_map(move |name_like| {
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use either::Either;
|
||||
use hir::{
|
||||
AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics,
|
||||
Trait,
|
||||
};
|
||||
use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait};
|
||||
use ide_db::{
|
||||
active_parameter::{callable_for_node, generic_def_for_node},
|
||||
documentation::{Documentation, HasDocs},
|
||||
|
@ -82,7 +79,7 @@ pub(crate) fn signature_help(
|
|||
// if the cursor is sandwiched between two space tokens and the call is unclosed
|
||||
// this prevents us from leaving the CallExpression
|
||||
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
|
||||
let token = sema.descend_into_macros_single(DescendPreference::None, token);
|
||||
let token = sema.descend_into_macros_single_exact(token);
|
||||
let edition =
|
||||
sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@ mod html;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use hir::{DescendPreference, Name, Semantics};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use hir::{InRealFile, Name, Semantics};
|
||||
use ide_db::{FxHashMap, RootDatabase, SymbolKind};
|
||||
use span::EditionedFileId;
|
||||
use syntax::{
|
||||
|
@ -399,19 +401,55 @@ fn traverse(
|
|||
// Attempt to descend tokens into macro-calls.
|
||||
let res = match element {
|
||||
NodeOrToken::Token(token) if token.kind() != COMMENT => {
|
||||
let token = if token.kind() == STRING {
|
||||
// for strings, try to prefer a string that has not been lost in a token
|
||||
// tree
|
||||
// FIXME: This should be done for everything, but check perf first
|
||||
sema.descend_into_macros(DescendPreference::SameKind, token)
|
||||
.into_iter()
|
||||
.max_by_key(|it| {
|
||||
it.parent().map_or(false, |it| it.kind() != TOKEN_TREE)
|
||||
})
|
||||
.unwrap()
|
||||
} else {
|
||||
sema.descend_into_macros_single(DescendPreference::SameKind, token)
|
||||
};
|
||||
let kind = token.kind();
|
||||
let text = token.text();
|
||||
let ident_kind = kind.is_any_identifier();
|
||||
|
||||
let mut t = None;
|
||||
let mut r = 0;
|
||||
sema.descend_into_macros_breakable(
|
||||
InRealFile::new(file_id, token.clone()),
|
||||
|tok| {
|
||||
let tok = tok.value;
|
||||
let tok_kind = tok.kind();
|
||||
|
||||
let exact_same_kind = tok_kind == kind;
|
||||
let both_idents =
|
||||
exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
|
||||
let same_text = tok.text() == text;
|
||||
// anything that mapped into a token tree has likely no semantic information
|
||||
let no_tt_parent =
|
||||
tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
|
||||
let my_rank = (both_idents as usize)
|
||||
| ((exact_same_kind as usize) << 1)
|
||||
| ((same_text as usize) << 2)
|
||||
| ((no_tt_parent as usize) << 3);
|
||||
|
||||
if my_rank > 0b1110 {
|
||||
// a rank of 0b1110 means that we have found a maximally interesting
|
||||
// token so stop early.
|
||||
t = Some(tok);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
// r = r.max(my_rank);
|
||||
// t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok));
|
||||
match &mut t {
|
||||
Some(prev) if r < my_rank => {
|
||||
*prev = tok;
|
||||
r = my_rank;
|
||||
}
|
||||
Some(_) => (),
|
||||
None => {
|
||||
r = my_rank;
|
||||
t = Some(tok)
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
},
|
||||
);
|
||||
|
||||
let token = t.unwrap_or(token);
|
||||
match token.parent().and_then(ast::NameLike::cast) {
|
||||
// Remap the token into the wrapping single token nodes
|
||||
Some(parent) => match (token.kind(), parent.syntax().kind()) {
|
||||
|
|
|
@ -100,7 +100,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||
<span class="brace">}</span><span class="semicolon">;</span>
|
||||
<span class="brace">}</span>
|
||||
|
||||
<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="string_literal macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
|
||||
<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="comma macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
|
||||
|
||||
<span class="keyword">struct</span> <span class="struct declaration">S</span><span class="angle"><</span><span class="type_param declaration">T</span><span class="angle">></span><span class="parenthesis">(</span><span class="type_param">T</span><span class="parenthesis">)</span><span class="semicolon">;</span>
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
|
||||
|
|
Loading…
Reference in a new issue