mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-29 02:52:11 +00:00
Cleanup highlighting macro-def handling
This commit is contained in:
parent
fe84446166
commit
487d682204
8 changed files with 197 additions and 340 deletions
|
|
@ -508,9 +508,7 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_derive_annotated(&self, adt: &ast::Adt) -> bool {
|
pub fn is_derive_annotated(&self, adt: InFile<&ast::Adt>) -> bool {
|
||||||
let file_id = self.find_file(adt.syntax()).file_id;
|
|
||||||
let adt = InFile::new(file_id, adt);
|
|
||||||
self.with_ctx(|ctx| ctx.has_derives(adt))
|
self.with_ctx(|ctx| ctx.has_derives(adt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,10 +549,8 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
res.is_empty().not().then_some(res)
|
res.is_empty().not().then_some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
|
pub fn is_attr_macro_call(&self, item: InFile<&ast::Item>) -> bool {
|
||||||
let file_id = self.find_file(item.syntax()).file_id;
|
self.with_ctx(|ctx| ctx.item_to_macro_call(item).is_some())
|
||||||
let src = InFile::new(file_id, item);
|
|
||||||
self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand the macro call with a different token tree, mapping the `token_to_map` down into the
|
/// Expand the macro call with a different token tree, mapping the `token_to_map` down into the
|
||||||
|
|
@ -1526,8 +1522,13 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field)
|
self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Replace this with `resolve_macro_call2`
|
||||||
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> {
|
pub fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<Macro> {
|
||||||
let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
|
let macro_call = self.find_file(macro_call.syntax()).with_value(macro_call);
|
||||||
|
self.resolve_macro_call2(macro_call)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_macro_call2(&self, macro_call: InFile<&ast::MacroCall>) -> Option<Macro> {
|
||||||
self.with_ctx(|ctx| {
|
self.with_ctx(|ctx| {
|
||||||
ctx.macro_call_to_macro_call(macro_call)
|
ctx.macro_call_to_macro_call(macro_call)
|
||||||
.and_then(|call| macro_call_to_macro_id(ctx, call))
|
.and_then(|call| macro_call_to_macro_id(ctx, call))
|
||||||
|
|
@ -1538,8 +1539,8 @@ impl<'db> SemanticsImpl<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_proc_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
|
pub fn is_proc_macro_call(&self, macro_call: InFile<&ast::MacroCall>) -> bool {
|
||||||
self.resolve_macro_call(macro_call)
|
self.resolve_macro_call2(macro_call)
|
||||||
.is_some_and(|m| matches!(m.id, MacroId::ProcMacroId(..)))
|
.is_some_and(|m| matches!(m.id, MacroId::ProcMacroId(..)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ mod escape;
|
||||||
mod format;
|
mod format;
|
||||||
mod highlight;
|
mod highlight;
|
||||||
mod inject;
|
mod inject;
|
||||||
mod macro_;
|
|
||||||
|
|
||||||
mod html;
|
mod html;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -15,14 +14,14 @@ mod tests;
|
||||||
|
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
use hir::{InRealFile, Name, Semantics};
|
use hir::{InFile, InRealFile, Name, Semantics};
|
||||||
use ide_db::{FxHashMap, Ranker, RootDatabase, SymbolKind};
|
use ide_db::{FxHashMap, Ranker, RootDatabase, SymbolKind};
|
||||||
use span::EditionedFileId;
|
use span::EditionedFileId;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, IsString},
|
ast::{self, IsString},
|
||||||
AstNode, AstToken, NodeOrToken,
|
AstNode, AstToken, NodeOrToken,
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
SyntaxNode, TextRange, WalkEvent, T,
|
SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -30,7 +29,6 @@ use crate::{
|
||||||
escape::{highlight_escape_byte, highlight_escape_char, highlight_escape_string},
|
escape::{highlight_escape_byte, highlight_escape_char, highlight_escape_string},
|
||||||
format::highlight_format_string,
|
format::highlight_format_string,
|
||||||
highlights::Highlights,
|
highlights::Highlights,
|
||||||
macro_::MacroHighlighter,
|
|
||||||
tags::Highlight,
|
tags::Highlight,
|
||||||
},
|
},
|
||||||
FileId, HlMod, HlOperator, HlPunct, HlTag,
|
FileId, HlMod, HlOperator, HlPunct, HlTag,
|
||||||
|
|
@ -221,7 +219,7 @@ pub(crate) fn highlight(
|
||||||
Some(it) => it.krate(),
|
Some(it) => it.krate(),
|
||||||
None => return hl.to_vec(),
|
None => return hl.to_vec(),
|
||||||
};
|
};
|
||||||
traverse(&mut hl, &sema, config, file_id, &root, krate, range_to_highlight);
|
traverse(&mut hl, &sema, config, InRealFile::new(file_id, &root), krate, range_to_highlight);
|
||||||
hl.to_vec()
|
hl.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,8 +227,7 @@ fn traverse(
|
||||||
hl: &mut Highlights,
|
hl: &mut Highlights,
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
config: HighlightConfig,
|
config: HighlightConfig,
|
||||||
file_id: EditionedFileId,
|
InRealFile { file_id, value: root }: InRealFile<&SyntaxNode>,
|
||||||
root: &SyntaxNode,
|
|
||||||
krate: hir::Crate,
|
krate: hir::Crate,
|
||||||
range_to_highlight: TextRange,
|
range_to_highlight: TextRange,
|
||||||
) {
|
) {
|
||||||
|
|
@ -252,8 +249,6 @@ fn traverse(
|
||||||
|
|
||||||
let mut tt_level = 0;
|
let mut tt_level = 0;
|
||||||
let mut attr_or_derive_item = None;
|
let mut attr_or_derive_item = None;
|
||||||
let mut current_macro: Option<ast::Macro> = None;
|
|
||||||
let mut macro_highlighter = MacroHighlighter::default();
|
|
||||||
|
|
||||||
// FIXME: these are not perfectly accurate, we determine them by the real file's syntax tree
|
// FIXME: these are not perfectly accurate, we determine them by the real file's syntax tree
|
||||||
// an attribute nested in a macro call will not emit `inside_attribute`
|
// an attribute nested in a macro call will not emit `inside_attribute`
|
||||||
|
|
@ -263,7 +258,8 @@ fn traverse(
|
||||||
|
|
||||||
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
||||||
// If in macro, expand it first and highlight the expanded code.
|
// If in macro, expand it first and highlight the expanded code.
|
||||||
for event in root.preorder_with_tokens() {
|
let mut preorder = root.preorder_with_tokens();
|
||||||
|
while let Some(event) = preorder.next() {
|
||||||
use WalkEvent::{Enter, Leave};
|
use WalkEvent::{Enter, Leave};
|
||||||
|
|
||||||
let range = match &event {
|
let range = match &event {
|
||||||
|
|
@ -275,16 +271,11 @@ fn traverse(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set macro and attribute highlighting states
|
|
||||||
match event.clone() {
|
match event.clone() {
|
||||||
Enter(NodeOrToken::Node(node))
|
Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
|
||||||
if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
|
|
||||||
{
|
|
||||||
tt_level += 1;
|
tt_level += 1;
|
||||||
}
|
}
|
||||||
Leave(NodeOrToken::Node(node))
|
Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
|
||||||
if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
|
|
||||||
{
|
|
||||||
tt_level -= 1;
|
tt_level -= 1;
|
||||||
}
|
}
|
||||||
Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
|
Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
|
||||||
|
|
@ -297,28 +288,19 @@ fn traverse(
|
||||||
Enter(NodeOrToken::Node(node)) => {
|
Enter(NodeOrToken::Node(node)) => {
|
||||||
if let Some(item) = ast::Item::cast(node.clone()) {
|
if let Some(item) = ast::Item::cast(node.clone()) {
|
||||||
match item {
|
match item {
|
||||||
ast::Item::MacroRules(mac) => {
|
|
||||||
macro_highlighter.init();
|
|
||||||
current_macro = Some(mac.into());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ast::Item::MacroDef(mac) => {
|
|
||||||
macro_highlighter.init();
|
|
||||||
current_macro = Some(mac.into());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ast::Item::Fn(_) | ast::Item::Const(_) | ast::Item::Static(_) => {
|
ast::Item::Fn(_) | ast::Item::Const(_) | ast::Item::Static(_) => {
|
||||||
bindings_shadow_count.clear()
|
bindings_shadow_count.clear()
|
||||||
}
|
}
|
||||||
ast::Item::MacroCall(ref macro_call) => {
|
ast::Item::MacroCall(ref macro_call) => {
|
||||||
inside_macro_call = true;
|
inside_macro_call = true;
|
||||||
inside_proc_macro_call = sema.is_proc_macro_call(macro_call);
|
inside_proc_macro_call =
|
||||||
|
sema.is_proc_macro_call(InFile::new(file_id.into(), macro_call));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr_or_derive_item.is_none() {
|
if attr_or_derive_item.is_none() {
|
||||||
if sema.is_attr_macro_call(&item) {
|
if sema.is_attr_macro_call(InFile::new(file_id.into(), &item)) {
|
||||||
attr_or_derive_item = Some(AttrOrDerive::Attr(item));
|
attr_or_derive_item = Some(AttrOrDerive::Attr(item));
|
||||||
} else {
|
} else {
|
||||||
let adt = match item {
|
let adt = match item {
|
||||||
|
|
@ -328,7 +310,10 @@ fn traverse(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
match adt {
|
match adt {
|
||||||
Some(adt) if sema.is_derive_annotated(&adt) => {
|
Some(adt)
|
||||||
|
if sema
|
||||||
|
.is_derive_annotated(InFile::new(file_id.into(), &adt)) =>
|
||||||
|
{
|
||||||
attr_or_derive_item =
|
attr_or_derive_item =
|
||||||
Some(AttrOrDerive::Derive(ast::Item::from(adt)));
|
Some(AttrOrDerive::Derive(ast::Item::from(adt)));
|
||||||
}
|
}
|
||||||
|
|
@ -340,16 +325,6 @@ fn traverse(
|
||||||
}
|
}
|
||||||
Leave(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
|
Leave(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
|
||||||
match ast::Item::cast(node.clone()) {
|
match ast::Item::cast(node.clone()) {
|
||||||
Some(ast::Item::MacroRules(mac)) => {
|
|
||||||
assert_eq!(current_macro, Some(mac.into()));
|
|
||||||
current_macro = None;
|
|
||||||
macro_highlighter = MacroHighlighter::default();
|
|
||||||
}
|
|
||||||
Some(ast::Item::MacroDef(mac)) => {
|
|
||||||
assert_eq!(current_macro, Some(mac.into()));
|
|
||||||
current_macro = None;
|
|
||||||
macro_highlighter = MacroHighlighter::default();
|
|
||||||
}
|
|
||||||
Some(item)
|
Some(item)
|
||||||
if attr_or_derive_item.as_ref().is_some_and(|it| *it.item() == item) =>
|
if attr_or_derive_item.as_ref().is_some_and(|it| *it.item() == item) =>
|
||||||
{
|
{
|
||||||
|
|
@ -379,12 +354,6 @@ fn traverse(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if current_macro.is_some() {
|
|
||||||
if let Some(tok) = element.as_token() {
|
|
||||||
macro_highlighter.advance(tok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let element = match element.clone() {
|
let element = match element.clone() {
|
||||||
NodeOrToken::Node(n) => match ast::NameLike::cast(n) {
|
NodeOrToken::Node(n) => match ast::NameLike::cast(n) {
|
||||||
Some(n) => NodeOrToken::Node(n),
|
Some(n) => NodeOrToken::Node(n),
|
||||||
|
|
@ -392,7 +361,7 @@ fn traverse(
|
||||||
},
|
},
|
||||||
NodeOrToken::Token(t) => NodeOrToken::Token(t),
|
NodeOrToken::Token(t) => NodeOrToken::Token(t),
|
||||||
};
|
};
|
||||||
let token = element.as_token().cloned();
|
let original_token = element.as_token().cloned();
|
||||||
|
|
||||||
// Descending tokens into macros is expensive even if no descending occurs, so make sure
|
// Descending tokens into macros is expensive even if no descending occurs, so make sure
|
||||||
// that we actually are in a position where descending is possible.
|
// that we actually are in a position where descending is possible.
|
||||||
|
|
@ -405,144 +374,52 @@ fn traverse(
|
||||||
|
|
||||||
let descended_element = if in_macro {
|
let descended_element = if in_macro {
|
||||||
// Attempt to descend tokens into macro-calls.
|
// Attempt to descend tokens into macro-calls.
|
||||||
let res = match element {
|
match element {
|
||||||
NodeOrToken::Token(token) if token.kind() != COMMENT => {
|
NodeOrToken::Token(token) => descend_token(sema, file_id, token),
|
||||||
let ranker = Ranker::from_token(&token);
|
n => n,
|
||||||
|
}
|
||||||
let mut t = None;
|
|
||||||
let mut r = 0;
|
|
||||||
sema.descend_into_macros_breakable(
|
|
||||||
InRealFile::new(file_id, token.clone()),
|
|
||||||
|tok, _ctx| {
|
|
||||||
// FIXME: Consider checking ctx transparency for being opaque?
|
|
||||||
let tok = tok.value;
|
|
||||||
let my_rank = ranker.rank_token(&tok);
|
|
||||||
|
|
||||||
if my_rank >= Ranker::MAX_RANK {
|
|
||||||
// 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()) {
|
|
||||||
(T![self] | T![ident], NAME | NAME_REF) => NodeOrToken::Node(parent),
|
|
||||||
(T![self] | T![super] | T![crate] | T![Self], NAME_REF) => {
|
|
||||||
NodeOrToken::Node(parent)
|
|
||||||
}
|
|
||||||
(INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
|
|
||||||
(LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
|
|
||||||
_ => NodeOrToken::Token(token),
|
|
||||||
},
|
|
||||||
None => NodeOrToken::Token(token),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e => e,
|
|
||||||
};
|
|
||||||
res
|
|
||||||
} else {
|
} else {
|
||||||
element
|
element
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: do proper macro def highlighting https://github.com/rust-lang/rust-analyzer/issues/6232
|
|
||||||
// Skip metavariables from being highlighted to prevent keyword highlighting in them
|
|
||||||
if descended_element.as_token().and_then(|t| macro_highlighter.highlight(t)).is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// string highlight injections, note this does not use the descended element as proc-macros
|
// string highlight injections, note this does not use the descended element as proc-macros
|
||||||
// can rewrite string literals which invalidates our indices
|
// can rewrite string literals which invalidates our indices
|
||||||
if let (Some(token), Some(descended_token)) = (token, descended_element.as_token()) {
|
if let (Some(original_token), Some(descended_token)) =
|
||||||
if ast::String::can_cast(token.kind()) && ast::String::can_cast(descended_token.kind())
|
(original_token, descended_element.as_token())
|
||||||
{
|
{
|
||||||
let string = ast::String::cast(token);
|
let control_flow = string_injections(
|
||||||
let string_to_highlight = ast::String::cast(descended_token.clone());
|
hl,
|
||||||
if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
|
sema,
|
||||||
if string.is_raw()
|
config,
|
||||||
&& inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some()
|
file_id,
|
||||||
{
|
krate,
|
||||||
continue;
|
original_token,
|
||||||
}
|
descended_token,
|
||||||
highlight_format_string(
|
);
|
||||||
hl,
|
if control_flow.is_break() {
|
||||||
sema,
|
continue;
|
||||||
krate,
|
|
||||||
&string,
|
|
||||||
&expanded_string,
|
|
||||||
range,
|
|
||||||
file_id.edition(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if !string.is_raw() {
|
|
||||||
highlight_escape_string(hl, &string, range.start());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ast::ByteString::can_cast(token.kind())
|
|
||||||
&& ast::ByteString::can_cast(descended_token.kind())
|
|
||||||
{
|
|
||||||
if let Some(byte_string) = ast::ByteString::cast(token) {
|
|
||||||
if !byte_string.is_raw() {
|
|
||||||
highlight_escape_string(hl, &byte_string, range.start());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ast::CString::can_cast(token.kind())
|
|
||||||
&& ast::CString::can_cast(descended_token.kind())
|
|
||||||
{
|
|
||||||
if let Some(c_string) = ast::CString::cast(token) {
|
|
||||||
if !c_string.is_raw() {
|
|
||||||
highlight_escape_string(hl, &c_string, range.start());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ast::Char::can_cast(token.kind())
|
|
||||||
&& ast::Char::can_cast(descended_token.kind())
|
|
||||||
{
|
|
||||||
let Some(char) = ast::Char::cast(token) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
highlight_escape_char(hl, &char, range.start())
|
|
||||||
} else if ast::Byte::can_cast(token.kind())
|
|
||||||
&& ast::Byte::can_cast(descended_token.kind())
|
|
||||||
{
|
|
||||||
let Some(byte) = ast::Byte::cast(token) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
highlight_escape_byte(hl, &byte, range.start())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let element = match descended_element {
|
let element = match descended_element {
|
||||||
NodeOrToken::Node(name_like) => highlight::name_like(
|
NodeOrToken::Node(name_like) => {
|
||||||
sema,
|
let hl = highlight::name_like(
|
||||||
krate,
|
sema,
|
||||||
&mut bindings_shadow_count,
|
krate,
|
||||||
config.syntactic_name_ref_highlighting,
|
&mut bindings_shadow_count,
|
||||||
name_like,
|
config.syntactic_name_ref_highlighting,
|
||||||
file_id.edition(),
|
name_like,
|
||||||
),
|
file_id.edition(),
|
||||||
|
);
|
||||||
|
if hl.is_some() && !in_macro {
|
||||||
|
// skip highlighting the contained token of our name-like node
|
||||||
|
// as that would potentially overwrite our result
|
||||||
|
preorder.skip_subtree();
|
||||||
|
}
|
||||||
|
hl
|
||||||
|
}
|
||||||
NodeOrToken::Token(token) => {
|
NodeOrToken::Token(token) => {
|
||||||
highlight::token(sema, token, file_id.edition()).zip(Some(None))
|
highlight::token(sema, token, file_id.edition(), tt_level > 0).zip(Some(None))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some((mut highlight, binding_hash)) = element {
|
if let Some((mut highlight, binding_hash)) = element {
|
||||||
|
|
@ -551,13 +428,6 @@ fn traverse(
|
||||||
// let the editor do its highlighting for these tokens instead
|
// let the editor do its highlighting for these tokens instead
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if highlight.tag == HlTag::UnresolvedReference
|
|
||||||
&& matches!(attr_or_derive_item, Some(AttrOrDerive::Derive(_)) if inside_attribute)
|
|
||||||
{
|
|
||||||
// do not emit unresolved references in derive helpers if the token mapping maps to
|
|
||||||
// something unresolvable. FIXME: There should be a way to prevent that
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply config filtering
|
// apply config filtering
|
||||||
if !filter_by_config(&mut highlight, config) {
|
if !filter_by_config(&mut highlight, config) {
|
||||||
|
|
@ -579,6 +449,115 @@ fn traverse(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn string_injections(
|
||||||
|
hl: &mut Highlights,
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
config: HighlightConfig,
|
||||||
|
file_id: EditionedFileId,
|
||||||
|
krate: hir::Crate,
|
||||||
|
token: SyntaxToken,
|
||||||
|
descended_token: &SyntaxToken,
|
||||||
|
) -> ControlFlow<()> {
|
||||||
|
if ast::String::can_cast(token.kind()) && ast::String::can_cast(descended_token.kind()) {
|
||||||
|
let string = ast::String::cast(token);
|
||||||
|
let string_to_highlight = ast::String::cast(descended_token.clone());
|
||||||
|
if let Some((string, descended_string)) = string.zip(string_to_highlight) {
|
||||||
|
if string.is_raw()
|
||||||
|
&& inject::ra_fixture(hl, sema, config, &string, &descended_string).is_some()
|
||||||
|
{
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
}
|
||||||
|
highlight_format_string(hl, sema, krate, &string, &descended_string, file_id.edition());
|
||||||
|
|
||||||
|
if !string.is_raw() {
|
||||||
|
highlight_escape_string(hl, &string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ast::ByteString::can_cast(token.kind())
|
||||||
|
&& ast::ByteString::can_cast(descended_token.kind())
|
||||||
|
{
|
||||||
|
if let Some(byte_string) = ast::ByteString::cast(token) {
|
||||||
|
if !byte_string.is_raw() {
|
||||||
|
highlight_escape_string(hl, &byte_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ast::CString::can_cast(token.kind()) && ast::CString::can_cast(descended_token.kind())
|
||||||
|
{
|
||||||
|
if let Some(c_string) = ast::CString::cast(token) {
|
||||||
|
if !c_string.is_raw() {
|
||||||
|
highlight_escape_string(hl, &c_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ast::Char::can_cast(token.kind()) && ast::Char::can_cast(descended_token.kind()) {
|
||||||
|
let Some(char) = ast::Char::cast(token) else {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
};
|
||||||
|
|
||||||
|
highlight_escape_char(hl, &char)
|
||||||
|
} else if ast::Byte::can_cast(token.kind()) && ast::Byte::can_cast(descended_token.kind()) {
|
||||||
|
let Some(byte) = ast::Byte::cast(token) else {
|
||||||
|
return ControlFlow::Break(());
|
||||||
|
};
|
||||||
|
|
||||||
|
highlight_escape_byte(hl, &byte)
|
||||||
|
}
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn descend_token(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
file_id: EditionedFileId,
|
||||||
|
token: SyntaxToken,
|
||||||
|
) -> NodeOrToken<ast::NameLike, SyntaxToken> {
|
||||||
|
if token.kind() == COMMENT {
|
||||||
|
return NodeOrToken::Token(token);
|
||||||
|
}
|
||||||
|
let ranker = Ranker::from_token(&token);
|
||||||
|
|
||||||
|
let mut t = None;
|
||||||
|
let mut r = 0;
|
||||||
|
sema.descend_into_macros_breakable(InRealFile::new(file_id, token.clone()), |tok, _ctx| {
|
||||||
|
// FIXME: Consider checking ctx transparency for being opaque?
|
||||||
|
let tok = tok.value;
|
||||||
|
let my_rank = ranker.rank_token(&tok);
|
||||||
|
|
||||||
|
if my_rank >= Ranker::MAX_RANK {
|
||||||
|
// 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()) {
|
||||||
|
(T![self] | T![ident], NAME | NAME_REF) => NodeOrToken::Node(parent),
|
||||||
|
(T![self] | T![super] | T![crate] | T![Self], NAME_REF) => NodeOrToken::Node(parent),
|
||||||
|
(INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
|
||||||
|
(LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
|
||||||
|
_ => NodeOrToken::Token(token),
|
||||||
|
},
|
||||||
|
None => NodeOrToken::Token(token),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn filter_by_config(highlight: &mut Highlight, config: HighlightConfig) -> bool {
|
fn filter_by_config(highlight: &mut Highlight, config: HighlightConfig) -> bool {
|
||||||
match &mut highlight.tag {
|
match &mut highlight.tag {
|
||||||
HlTag::StringLiteral if !config.strings => return false,
|
HlTag::StringLiteral if !config.strings => return false,
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,9 @@ use crate::{HlRange, HlTag};
|
||||||
use syntax::ast::{Byte, Char, IsString};
|
use syntax::ast::{Byte, Char, IsString};
|
||||||
use syntax::{AstToken, TextRange, TextSize};
|
use syntax::{AstToken, TextRange, TextSize};
|
||||||
|
|
||||||
pub(super) fn highlight_escape_string<T: IsString>(
|
pub(super) fn highlight_escape_string<T: IsString>(stack: &mut Highlights, string: &T) {
|
||||||
stack: &mut Highlights,
|
|
||||||
string: &T,
|
|
||||||
start: TextSize,
|
|
||||||
) {
|
|
||||||
let text = string.text();
|
let text = string.text();
|
||||||
|
let start = string.syntax().text_range().start();
|
||||||
string.escaped_char_ranges(&mut |piece_range, char| {
|
string.escaped_char_ranges(&mut |piece_range, char| {
|
||||||
if text[piece_range.start().into()..].starts_with('\\') {
|
if text[piece_range.start().into()..].starts_with('\\') {
|
||||||
let highlight = match char {
|
let highlight = match char {
|
||||||
|
|
@ -25,7 +22,7 @@ pub(super) fn highlight_escape_string<T: IsString>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) {
|
pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char) {
|
||||||
if char.value().is_err() {
|
if char.value().is_err() {
|
||||||
// We do not emit invalid escapes highlighting here. The lexer would likely be in a bad
|
// We do not emit invalid escapes highlighting here. The lexer would likely be in a bad
|
||||||
// state and this token contains junk, since `'` is not a reliable delimiter (consider
|
// state and this token contains junk, since `'` is not a reliable delimiter (consider
|
||||||
|
|
@ -42,11 +39,14 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = TextRange::at(start + TextSize::from(1), TextSize::from(text.len() as u32));
|
let range = TextRange::at(
|
||||||
|
char.syntax().text_range().start() + TextSize::from(1),
|
||||||
|
TextSize::from(text.len() as u32),
|
||||||
|
);
|
||||||
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) {
|
pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte) {
|
||||||
if byte.value().is_err() {
|
if byte.value().is_err() {
|
||||||
// See `highlight_escape_char` for why no error highlighting here.
|
// See `highlight_escape_char` for why no error highlighting here.
|
||||||
return;
|
return;
|
||||||
|
|
@ -61,6 +61,9 @@ pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start:
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = TextRange::at(start + TextSize::from(2), TextSize::from(text.len() as u32));
|
let range = TextRange::at(
|
||||||
|
byte.syntax().text_range().start() + TextSize::from(2),
|
||||||
|
TextSize::from(text.len() as u32),
|
||||||
|
);
|
||||||
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
stack.add(HlRange { range, highlight: HlTag::EscapeSequence.into(), binding_hash: None })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ide_db::{
|
||||||
SymbolKind,
|
SymbolKind,
|
||||||
};
|
};
|
||||||
use span::Edition;
|
use span::Edition;
|
||||||
use syntax::{ast, TextRange};
|
use syntax::{ast, AstToken};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
syntax_highlighting::{highlight::highlight_def, highlights::Highlights},
|
syntax_highlighting::{highlight::highlight_def, highlights::Highlights},
|
||||||
|
|
@ -18,15 +18,15 @@ pub(super) fn highlight_format_string(
|
||||||
krate: hir::Crate,
|
krate: hir::Crate,
|
||||||
string: &ast::String,
|
string: &ast::String,
|
||||||
expanded_string: &ast::String,
|
expanded_string: &ast::String,
|
||||||
range: TextRange,
|
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
) {
|
) {
|
||||||
if is_format_string(expanded_string) {
|
if is_format_string(expanded_string) {
|
||||||
|
let start = string.syntax().text_range().start();
|
||||||
// FIXME: Replace this with the HIR info we have now.
|
// FIXME: Replace this with the HIR info we have now.
|
||||||
lex_format_specifiers(string, &mut |piece_range, kind| {
|
lex_format_specifiers(string, &mut |piece_range, kind| {
|
||||||
if let Some(highlight) = highlight_format_specifier(kind) {
|
if let Some(highlight) = highlight_format_specifier(kind) {
|
||||||
stack.add(HlRange {
|
stack.add(HlRange {
|
||||||
range: piece_range + range.start(),
|
range: piece_range + start,
|
||||||
highlight: highlight.into(),
|
highlight: highlight.into(),
|
||||||
binding_hash: None,
|
binding_hash: None,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ pub(super) fn token(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
|
in_tt: bool,
|
||||||
) -> Option<Highlight> {
|
) -> Option<Highlight> {
|
||||||
if let Some(comment) = ast::Comment::cast(token.clone()) {
|
if let Some(comment) = ast::Comment::cast(token.clone()) {
|
||||||
let h = HlTag::Comment;
|
let h = HlTag::Comment;
|
||||||
|
|
@ -40,13 +41,20 @@ pub(super) fn token(
|
||||||
INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
|
INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
|
||||||
BYTE => HlTag::ByteLiteral.into(),
|
BYTE => HlTag::ByteLiteral.into(),
|
||||||
CHAR => HlTag::CharLiteral.into(),
|
CHAR => HlTag::CharLiteral.into(),
|
||||||
IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => {
|
IDENT if in_tt => {
|
||||||
// from this point on we are inside a token tree, this only happens for identifiers
|
// from this point on we are inside a token tree, this only happens for identifiers
|
||||||
// that were not mapped down into macro invocations
|
// that were not mapped down into macro invocations
|
||||||
HlTag::None.into()
|
HlTag::None.into()
|
||||||
}
|
}
|
||||||
p if p.is_punct() => punctuation(sema, token, p),
|
p if p.is_punct() => punctuation(sema, token, p),
|
||||||
k if k.is_keyword(edition) => keyword(sema, token, k)?,
|
k if k.is_keyword(edition) => {
|
||||||
|
if in_tt && token.prev_token().is_some_and(|t| t.kind() == T![$]) {
|
||||||
|
// we are likely within a macro definition where our keyword is a fragment name
|
||||||
|
HlTag::None.into()
|
||||||
|
} else {
|
||||||
|
keyword(sema, token, k)?
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some(highlight)
|
Some(highlight)
|
||||||
|
|
@ -214,12 +222,6 @@ fn keyword(
|
||||||
T![true] | T![false] => HlTag::BoolLiteral.into(),
|
T![true] | T![false] => HlTag::BoolLiteral.into(),
|
||||||
// crate is handled just as a token if it's in an `extern crate`
|
// crate is handled just as a token if it's in an `extern crate`
|
||||||
T![crate] if parent_matches::<ast::ExternCrate>(&token) => h,
|
T![crate] if parent_matches::<ast::ExternCrate>(&token) => h,
|
||||||
// self, crate, super and `Self` are handled as either a Name or NameRef already, unless they
|
|
||||||
// are inside unmapped token trees
|
|
||||||
T![self] | T![crate] | T![super] | T![Self] if parent_matches::<ast::NameRef>(&token) => {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
T![self] if parent_matches::<ast::Name>(&token) => return None,
|
|
||||||
T![ref] => match token.parent().and_then(ast::IdentPat::cast) {
|
T![ref] => match token.parent().and_then(ast::IdentPat::cast) {
|
||||||
Some(ident) if sema.is_unsafe_ident_pat(&ident) => h | HlMod::Unsafe,
|
Some(ident) if sema.is_unsafe_ident_pat(&ident) => h | HlMod::Unsafe,
|
||||||
_ => h,
|
_ => h,
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
//! Syntax highlighting for macro_rules!.
|
|
||||||
use syntax::{SyntaxKind, SyntaxToken, TextRange, T};
|
|
||||||
|
|
||||||
use crate::{HlRange, HlTag};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(super) struct MacroHighlighter {
|
|
||||||
state: Option<MacroMatcherParseState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MacroHighlighter {
|
|
||||||
pub(super) fn init(&mut self) {
|
|
||||||
self.state = Some(MacroMatcherParseState::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn advance(&mut self, token: &SyntaxToken) {
|
|
||||||
if let Some(state) = self.state.as_mut() {
|
|
||||||
update_macro_state(state, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn highlight(&self, token: &SyntaxToken) -> Option<HlRange> {
|
|
||||||
if let Some(state) = self.state.as_ref() {
|
|
||||||
if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
|
|
||||||
if let Some(range) = is_metavariable(token) {
|
|
||||||
return Some(HlRange {
|
|
||||||
range,
|
|
||||||
highlight: HlTag::UnresolvedReference.into(),
|
|
||||||
binding_hash: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MacroMatcherParseState {
|
|
||||||
/// Opening and corresponding closing bracket of the matcher or expander of the current rule
|
|
||||||
paren_ty: Option<(SyntaxKind, SyntaxKind)>,
|
|
||||||
paren_level: usize,
|
|
||||||
rule_state: RuleState,
|
|
||||||
/// Whether we are inside the outer `{` `}` macro block that holds the rules
|
|
||||||
in_invoc_body: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MacroMatcherParseState {
|
|
||||||
fn default() -> Self {
|
|
||||||
MacroMatcherParseState {
|
|
||||||
paren_ty: None,
|
|
||||||
paren_level: 0,
|
|
||||||
in_invoc_body: false,
|
|
||||||
rule_state: RuleState::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
enum RuleState {
|
|
||||||
Matcher,
|
|
||||||
Expander,
|
|
||||||
Between,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RuleState {
|
|
||||||
fn transition(&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
RuleState::Matcher => RuleState::Between,
|
|
||||||
RuleState::Expander => RuleState::None,
|
|
||||||
RuleState::Between => RuleState::Expander,
|
|
||||||
RuleState::None => RuleState::Matcher,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
|
|
||||||
if !state.in_invoc_body {
|
|
||||||
if tok.kind() == T!['{'] || tok.kind() == T!['('] {
|
|
||||||
state.in_invoc_body = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match state.paren_ty {
|
|
||||||
Some((open, close)) => {
|
|
||||||
if tok.kind() == open {
|
|
||||||
state.paren_level += 1;
|
|
||||||
} else if tok.kind() == close {
|
|
||||||
state.paren_level -= 1;
|
|
||||||
if state.paren_level == 0 {
|
|
||||||
state.rule_state.transition();
|
|
||||||
state.paren_ty = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
match tok.kind() {
|
|
||||||
T!['('] => {
|
|
||||||
state.paren_ty = Some((T!['('], T![')']));
|
|
||||||
}
|
|
||||||
T!['{'] => {
|
|
||||||
state.paren_ty = Some((T!['{'], T!['}']));
|
|
||||||
}
|
|
||||||
T!['['] => {
|
|
||||||
state.paren_ty = Some((T!['['], T![']']));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
if state.paren_ty.is_some() {
|
|
||||||
state.paren_level = 1;
|
|
||||||
state.rule_state.transition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_metavariable(token: &SyntaxToken) -> Option<TextRange> {
|
|
||||||
match token.kind() {
|
|
||||||
kind if kind.is_any_identifier() => {
|
|
||||||
if let Some(_dollar) = token.prev_token().filter(|t| t.kind() == T![$]) {
|
|
||||||
return Some(token.text_range());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
@ -156,7 +156,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||||
<span class="brace">}</span>
|
<span class="brace">}</span>
|
||||||
|
|
||||||
<span class="comment documentation">/// ```</span>
|
<span class="comment documentation">/// ```</span>
|
||||||
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected public">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="operator injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr </span><span class="brace injected">}</span><span class="brace injected">}</span>
|
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="keyword injected">macro_rules</span><span class="macro_bang injected">!</span><span class="none injected"> </span><span class="macro declaration injected public">noop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="parenthesis injected">(</span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="colon injected">:</span><span class="none injected">expr</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">=</span><span class="operator injected">></span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="punctuation injected">$</span><span class="none injected">expr</span><span class="none injected"> </span><span class="brace injected">}</span><span class="brace injected">}</span>
|
||||||
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected public">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected macro">(</span><span class="numeric_literal injected macro">1</span><span class="parenthesis injected macro">)</span><span class="semicolon injected">;</span>
|
<span class="comment documentation">///</span><span class="comment documentation"> </span><span class="macro injected public">noop</span><span class="macro_bang injected">!</span><span class="parenthesis injected macro">(</span><span class="numeric_literal injected macro">1</span><span class="parenthesis injected macro">)</span><span class="semicolon injected">;</span>
|
||||||
<span class="comment documentation">/// ```</span>
|
<span class="comment documentation">/// ```</span>
|
||||||
<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration public">noop</span> <span class="brace">{</span>
|
<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration public">noop</span> <span class="brace">{</span>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||||
<span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="method associated consuming">baz</span><span class="semicolon">;</span>
|
<span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="method associated consuming">baz</span><span class="semicolon">;</span>
|
||||||
|
|
||||||
<span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
|
<span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
|
||||||
<span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field">0</span><span class="semicolon">;</span>
|
<span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="operator">.</span><span class="field library">0</span><span class="semicolon">;</span>
|
||||||
|
|
||||||
<span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
|
<span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue