From 3d9997889bfe536a96e70535ab208a6e7ff3bc12 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Tue, 23 Jun 2020 19:07:42 +1000 Subject: SSR: Add initial support for placeholder constraints --- Cargo.lock | 1 + crates/ra_ide/src/ssr.rs | 12 ++++++ crates/ra_ssr/Cargo.toml | 1 + crates/ra_ssr/src/matching.rs | 39 +++++++++++++++++- crates/ra_ssr/src/parsing.rs | 92 ++++++++++++++++++++++++++++++++++++++++--- crates/ra_ssr/src/tests.rs | 17 ++++++++ 6 files changed, 156 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1d2475e2..61ae8157a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,6 +1248,7 @@ dependencies = [ "ra_syntax", "ra_text_edit", "rustc-hash", + "test_utils", ] [[package]] diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 9f8e540c0..b3e9e5dfe 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs @@ -10,6 +10,18 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule}; // The syntax for a structural search replace command is ` ==>> `. // A `$` placeholder in the search pattern will match any AST node and `$` will reference it in the replacement. // Within a macro call, a placeholder will match up until whatever token follows the placeholder. +// +// Placeholders may be given constraints by writing them as `${::...}`. +// +// Supported constraints: +// +// |=== +// | Constraint | Restricts placeholder +// +// | kind(literal) | Is a literal (e.g. `42` or `"forty two"`) +// | not(a) | Negates the constraint `a` +// |=== +// // Available via the command `rust-analyzer.ssr`. // // ```rust diff --git a/crates/ra_ssr/Cargo.toml b/crates/ra_ssr/Cargo.toml index 3c2f15a83..fe098aaee 100644 --- a/crates/ra_ssr/Cargo.toml +++ b/crates/ra_ssr/Cargo.toml @@ -17,3 +17,4 @@ ra_db = { path = "../ra_db" } ra_ide_db = { path = "../ra_ide_db" } hir = { path = "../ra_hir", package = "ra_hir" } rustc-hash = "1.1.0" +test_utils = { path = "../test_utils" } diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index 53d802e77..ce53d46d2 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs @@ -2,7 +2,7 @@ //! process of matching, placeholder values are recorded. use crate::{ - parsing::{Placeholder, SsrTemplate}, + parsing::{Constraint, NodeKind, Placeholder, SsrTemplate}, SsrMatches, SsrPattern, SsrRule, }; use hir::Semantics; @@ -11,6 +11,7 @@ use ra_syntax::ast::{AstNode, AstToken}; use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken}; use rustc_hash::FxHashMap; use std::{cell::Cell, iter::Peekable}; +use test_utils::mark; // Creates a match error. If we're currently attempting to match some code that we thought we were // going to match, as indicated by the --debug-snippet flag, then populate the reason field. @@ -169,6 +170,9 @@ impl<'db, 'sema> MatchState<'db, 'sema> { if let Some(placeholder) = match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone())) { + for constraint in &placeholder.constraints { + self.check_constraint(constraint, code)?; + } if self.match_out.is_none() { return Ok(()); } @@ -292,6 +296,24 @@ impl<'db, 'sema> MatchState<'db, 'sema> { Ok(()) } + fn check_constraint( + &self, + constraint: &Constraint, + code: &SyntaxNode, + ) -> Result<(), MatchFailed> { + match constraint { + Constraint::Kind(kind) => { + kind.matches(code)?; + } + Constraint::Not(sub) => { + if self.check_constraint(&*sub, code).is_ok() { + fail_match!("Constraint {:?} failed for '{}'", constraint, code.text()); + } + } + } + Ok(()) + } + /// We want to allow the records to match in any order, so we have special matching logic for /// them. fn attempt_match_record_field_list( @@ -515,6 +537,21 @@ impl SsrPattern { } } +impl NodeKind { + fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { + let ok = match self { + Self::Literal => { + mark::hit!(literal_constraint); + ast::Literal::can_cast(node.kind()) + } + }; + if !ok { + fail_match!("Code '{}' isn't of kind {:?}", node.text(), self); + } + Ok(()) + } +} + // If `node` contains nothing but an ident then return it, otherwise return None. fn only_ident(element: SyntaxElement) -> Option { match element { diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs index 04d46bd32..0f4f88b7c 100644 --- a/crates/ra_ssr/src/parsing.rs +++ b/crates/ra_ssr/src/parsing.rs @@ -39,6 +39,18 @@ pub(crate) struct Placeholder { pub(crate) ident: SmolStr, /// A unique name used in place of this placeholder when we parse the pattern as Rust code. stand_in_name: String, + pub(crate) constraints: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum Constraint { + Kind(NodeKind), + Not(Box), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum NodeKind { + Literal, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -177,6 +189,9 @@ fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { if !defined_placeholders.contains(&placeholder.ident) { undefined.push(format!("${}", placeholder.ident)); } + if !placeholder.constraints.is_empty() { + bail!("Replacement placeholders cannot have constraints"); + } } } if !undefined.is_empty() { @@ -205,23 +220,90 @@ fn tokenize(source: &str) -> Result, SsrError> { fn parse_placeholder(tokens: &mut std::vec::IntoIter) -> Result { let mut name = None; + let mut constraints = Vec::new(); if let Some(token) = tokens.next() { match token.kind { SyntaxKind::IDENT => { name = Some(token.text); } + SyntaxKind::L_CURLY => { + let token = + tokens.next().ok_or_else(|| SsrError::new("Unexpected end of placeholder"))?; + if token.kind == SyntaxKind::IDENT { + name = Some(token.text); + } + loop { + let token = tokens + .next() + .ok_or_else(|| SsrError::new("Placeholder is missing closing brace '}'"))?; + match token.kind { + SyntaxKind::COLON => { + constraints.push(parse_constraint(tokens)?); + } + SyntaxKind::R_CURLY => break, + _ => bail!("Unexpected token while parsing placeholder: '{}'", token.text), + } + } + } _ => { - bail!("Placeholders should be $name"); + bail!("Placeholders should either be $name or ${name:constraints}"); } } } let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?; - Ok(Placeholder::new(name)) + Ok(Placeholder::new(name, constraints)) +} + +fn parse_constraint(tokens: &mut std::vec::IntoIter) -> Result { + let constraint_type = tokens + .next() + .ok_or_else(|| SsrError::new("Found end of placeholder while looking for a constraint"))? + .text + .to_string(); + match constraint_type.as_str() { + "kind" => { + expect_token(tokens, "(")?; + let t = tokens.next().ok_or_else(|| { + SsrError::new("Unexpected end of constraint while looking for kind") + })?; + if t.kind != SyntaxKind::IDENT { + bail!("Expected ident, found {:?} while parsing kind constraint", t.kind); + } + expect_token(tokens, ")")?; + Ok(Constraint::Kind(NodeKind::from(&t.text)?)) + } + "not" => { + expect_token(tokens, "(")?; + let sub = parse_constraint(tokens)?; + expect_token(tokens, ")")?; + Ok(Constraint::Not(Box::new(sub))) + } + x => bail!("Unsupported constraint type '{}'", x), + } +} + +fn expect_token(tokens: &mut std::vec::IntoIter, expected: &str) -> Result<(), SsrError> { + if let Some(t) = tokens.next() { + if t.text == expected { + return Ok(()); + } + bail!("Expected {} found {}", expected, t.text); + } + bail!("Expected {} found end of stream"); +} + +impl NodeKind { + fn from(name: &SmolStr) -> Result { + Ok(match name.as_str() { + "literal" => NodeKind::Literal, + _ => bail!("Unknown node kind '{}'", name), + }) + } } impl Placeholder { - fn new(name: SmolStr) -> Self { - Self { stand_in_name: format!("__placeholder_{}", name), ident: name } + fn new(name: SmolStr, constraints: Vec) -> Self { + Self { stand_in_name: format!("__placeholder_{}", name), constraints, ident: name } } } @@ -241,7 +323,7 @@ mod tests { PatternElement::Token(Token { kind, text: SmolStr::new(text) }) } fn placeholder(name: &str) -> PatternElement { - PatternElement::Placeholder(Placeholder::new(SmolStr::new(name))) + PatternElement::Placeholder(Placeholder::new(SmolStr::new(name), Vec::new())) } let result: SsrRule = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap(); assert_eq!( diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index c692c97e2..9568d4432 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -1,5 +1,6 @@ use crate::{MatchFinder, SsrRule}; use ra_db::{FileId, SourceDatabaseExt}; +use test_utils::mark; fn parse_error_text(query: &str) -> String { format!("{}", query.parse::().unwrap_err()) @@ -301,6 +302,22 @@ fn match_pattern() { assert_matches("Some($a)", "fn f() {if let Some(x) = foo() {}}", &["Some(x)"]); } +#[test] +fn literal_constraint() { + mark::check!(literal_constraint); + let code = r#" + fn f1() { + let x1 = Some(42); + let x2 = Some("foo"); + let x3 = Some(x1); + let x4 = Some(40 + 2); + let x5 = Some(true); + } + "#; + assert_matches("Some(${a:kind(literal)})", code, &["Some(42)", "Some(\"foo\")", "Some(true)"]); + assert_matches("Some(${a:not(kind(literal))})", code, &["Some(x1)", "Some(40 + 2)"]); +} + #[test] fn match_reordered_struct_instantiation() { assert_matches( -- cgit v1.2.3 From 0bbd6428ad786cf60d5c79d0ac2aae6dac7dc1a5 Mon Sep 17 00:00:00 2001 From: BGluth Date: Fri, 26 Jun 2020 12:30:00 -0600 Subject: Added tests for no auto-completion on comments --- crates/ra_ide/src/completion/presentation.rs | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 4fdc2f0bb..b18279746 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -1516,4 +1516,54 @@ mod tests { "### ); } + + #[test] + fn no_keyword_autocompletion_on_line_comments() { + assert_debug_snapshot!( + do_completion( + r" + fn test() { + let x = 2; // A comment<|> + } + ", + CompletionKind::Keyword + ), + @r###" + [] + "### + ); + } + + #[test] + fn no_keyword_autocompletion_on_multi_line_comments() { + assert_debug_snapshot!( + do_completion( + r" + /* + Some multi-line comment<|> + */ + ", + CompletionKind::Keyword + ), + @r###" + [] + "### + ); + } + + #[test] + fn no_keyword_autocompletion_on_doc_comments() { + assert_debug_snapshot!( + do_completion( + r" + /// Some doc comment + /// let test<|> = 1 + ", + CompletionKind::Keyword + ), + @r###" + [] + "### + ); + } } -- cgit v1.2.3 From cc77bdf59ad99d3522663ebdb80be553f2767290 Mon Sep 17 00:00:00 2001 From: BGluth Date: Fri, 26 Jun 2020 20:09:11 -0600 Subject: Auto-completion no longer occurs on comments --- crates/ra_ide/src/completion/complete_keyword.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index 3b174f916..e599cc3d1 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use ra_syntax::ast; +use ra_syntax::{ast, SyntaxKind}; use crate::completion::{ CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, @@ -37,6 +37,10 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC } pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.token.kind() == SyntaxKind::COMMENT { + return; + } + let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { add_keyword(ctx, acc, "where", "where "); -- cgit v1.2.3 From 83588a1c452dff3ca6cd9e84cbe70a3b549fc851 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Thu, 2 Jul 2020 09:19:58 +1000 Subject: SSR: Use T! instead of SyntaxKind::* where possible --- crates/ra_ssr/src/parsing.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs index 0f4f88b7c..5ea125616 100644 --- a/crates/ra_ssr/src/parsing.rs +++ b/crates/ra_ssr/src/parsing.rs @@ -6,7 +6,7 @@ //! e.g. expressions, type references etc. use crate::{SsrError, SsrPattern, SsrRule}; -use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind}; +use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; use rustc_hash::{FxHashMap, FxHashSet}; use std::str::FromStr; @@ -161,7 +161,7 @@ fn parse_pattern(pattern_str: &str) -> Result, SsrError> { let mut placeholder_names = FxHashSet::default(); let mut tokens = tokenize(pattern_str)?.into_iter(); while let Some(token) = tokens.next() { - if token.kind == SyntaxKind::DOLLAR { + if token.kind == T![$] { let placeholder = parse_placeholder(&mut tokens)?; if !placeholder_names.insert(placeholder.ident.clone()) { bail!("Name `{}` repeats more than once", placeholder.ident); @@ -226,7 +226,7 @@ fn parse_placeholder(tokens: &mut std::vec::IntoIter) -> Result { name = Some(token.text); } - SyntaxKind::L_CURLY => { + T!['{'] => { let token = tokens.next().ok_or_else(|| SsrError::new("Unexpected end of placeholder"))?; if token.kind == SyntaxKind::IDENT { @@ -237,10 +237,10 @@ fn parse_placeholder(tokens: &mut std::vec::IntoIter) -> Result { + T![:] => { constraints.push(parse_constraint(tokens)?); } - SyntaxKind::R_CURLY => break, + T!['}'] => break, _ => bail!("Unexpected token while parsing placeholder: '{}'", token.text), } } @@ -330,24 +330,24 @@ mod tests { result.pattern.raw.tokens, vec![ token(SyntaxKind::IDENT, "foo"), - token(SyntaxKind::L_PAREN, "("), + token(T!['('], "("), placeholder("a"), - token(SyntaxKind::COMMA, ","), + token(T![,], ","), token(SyntaxKind::WHITESPACE, " "), placeholder("b"), - token(SyntaxKind::R_PAREN, ")"), + token(T![')'], ")"), ] ); assert_eq!( result.template.tokens, vec![ token(SyntaxKind::IDENT, "bar"), - token(SyntaxKind::L_PAREN, "("), + token(T!['('], "("), placeholder("b"), - token(SyntaxKind::COMMA, ","), + token(T![,], ","), token(SyntaxKind::WHITESPACE, " "), placeholder("a"), - token(SyntaxKind::R_PAREN, ")"), + token(T![')'], ")"), ] ); } -- cgit v1.2.3 From 69b6f6def525d33a60a3a992960d1085403d3b60 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 2 Jul 2020 04:56:50 +0300 Subject: Always install required nightly extension if current one is not nightly --- editors/code/src/main.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 5ceab8b44..1ad75a03c 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -152,13 +152,17 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi return; }; - const lastCheck = state.lastCheck; const now = Date.now(); + if (config.package.releaseTag === NIGHTLY_TAG) { + // Check if we should poll github api for the new nightly version + // if we haven't done it during the past hour + const lastCheck = state.lastCheck; - const anHour = 60 * 60 * 1000; - const shouldDownloadNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour; + const anHour = 60 * 60 * 1000; + const shouldCheckForNewNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour; - if (!shouldDownloadNightly) return; + if (!shouldCheckForNewNightly) return; + } const release = await fetchRelease("nightly").catch((e) => { log.error(e); -- cgit v1.2.3 From 6a6ce616aa8da460a145a8d535357adef9f51678 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 2 Jul 2020 05:19:02 +0300 Subject: Force showing extension activation error pop-up notification --- editors/code/src/main.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 5ceab8b44..ed26c887b 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -19,6 +19,16 @@ let ctx: Ctx | undefined; const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; export async function activate(context: vscode.ExtensionContext) { + // For some reason vscode not always shows pop-up error notifications + // when an extension fails to activate, so we do it explicitly by ourselves. + // FIXME: remove this bit of code once vscode fixes this issue: https://github.com/microsoft/vscode/issues/101242 + await tryActivate(context).catch(err => { + void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`); + throw err; + }); +} + +async function tryActivate(context: vscode.ExtensionContext) { // Register a "dumb" onEnter command for the case where server fails to // start. // @@ -58,9 +68,7 @@ export async function activate(context: vscode.ExtensionContext) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder === undefined) { - const err = "Cannot activate rust-analyzer when no folder is opened"; - void vscode.window.showErrorMessage(err); - throw new Error(err); + throw new Error("no folder is opened"); } // Note: we try to start the server before we activate type hints so that it -- cgit v1.2.3 From f1986be8fdcdc01c580b2d6e43574ae980d38e4b Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Thu, 2 Jul 2020 11:06:00 +0800 Subject: fix: correct pd/ppd/tfn/tmod completion doc https://github.com/rust-analyzer/rust-analyzer/blob/a33eefa3b26000b3018e6bb873f18dbe15ab4ab7/crates/ra_ide/src/completion/complete_snippet.rs#L23 --- crates/ra_ide/src/completion.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 69ea754b3..9ebb8ebb7 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -63,11 +63,11 @@ pub use crate::completion::{ // There also snippet completions: // // .Expressions -// - `pd` -> `println!("{:?}")` -// - `ppd` -> `println!("{:#?}")` +// - `pd` -> `eprintln!(" = {:?}", );")` +// - `ppd` -> `eprintln!(" = {:#?}", );` // // .Items -// - `tfn` -> `#[test] fn f(){}` +// - `tfn` -> `#[test] fn feature(){}` // - `tmod` -> // ```rust // #[cfg(test)] @@ -75,7 +75,7 @@ pub use crate::completion::{ // use super::*; // // #[test] -// fn test_fn() {} +// fn test_name() {} // } // ``` -- cgit v1.2.3