Rollup merge of #101497 - lnicola:rust-analyzer-2022-09-06, r=lnicola

⬆️ rust-analyzer

r? `@ghost`
This commit is contained in:
Yuki Okushi 2022-09-07 07:43:57 +09:00 committed by GitHub
commit 03b7494926
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 2922 additions and 896 deletions

View file

@ -14,7 +14,7 @@ env:
CARGO_NET_RETRY: 10 CARGO_NET_RETRY: 10
CI: 1 CI: 1
RUST_BACKTRACE: short RUST_BACKTRACE: short
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility" RUSTFLAGS: "-D warnings -W unreachable-pub -W bare-trait-objects"
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
jobs: jobs:
@ -110,9 +110,9 @@ jobs:
- run: npm ci - run: npm ci
working-directory: ./editors/code working-directory: ./editors/code
# - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } # - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
# if: runner.os == 'Linux' # if: runner.os == 'Linux'
# working-directory: ./editors/code # working-directory: ./editors/code
- run: npm run lint - run: npm run lint
working-directory: ./editors/code working-directory: ./editors/code

View file

@ -248,7 +248,7 @@ jobs:
if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
working-directory: ./editors/code working-directory: ./editors/code
# token from https://dev.azure.com/rust-analyzer/ # token from https://dev.azure.com/rust-analyzer/
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true
- name: Publish Extension (Code Marketplace, nightly) - name: Publish Extension (Code Marketplace, nightly)
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
@ -258,4 +258,4 @@ jobs:
- name: Publish Extension (OpenVSX, nightly) - name: Publish Extension (OpenVSX, nightly)
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
working-directory: ./editors/code working-directory: ./editors/code
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true

View file

@ -250,6 +250,10 @@ pub type PatSource = InFile<PatPtr>;
pub type LabelPtr = AstPtr<ast::Label>; pub type LabelPtr = AstPtr<ast::Label>;
pub type LabelSource = InFile<LabelPtr>; pub type LabelSource = InFile<LabelPtr>;
pub type FieldPtr = AstPtr<ast::RecordExprField>;
pub type FieldSource = InFile<FieldPtr>;
/// An item body together with the mapping from syntax nodes to HIR expression /// An item body together with the mapping from syntax nodes to HIR expression
/// IDs. This is needed to go from e.g. a position in a file to the HIR /// IDs. This is needed to go from e.g. a position in a file to the HIR
/// expression containing it; but for type inference etc., we want to operate on /// expression containing it; but for type inference etc., we want to operate on
@ -264,18 +268,18 @@ pub type LabelSource = InFile<LabelPtr>;
#[derive(Default, Debug, Eq, PartialEq)] #[derive(Default, Debug, Eq, PartialEq)]
pub struct BodySourceMap { pub struct BodySourceMap {
expr_map: FxHashMap<ExprSource, ExprId>, expr_map: FxHashMap<ExprSource, ExprId>,
expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>, expr_map_back: ArenaMap<ExprId, ExprSource>,
pat_map: FxHashMap<PatSource, PatId>, pat_map: FxHashMap<PatSource, PatId>,
pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, pat_map_back: ArenaMap<PatId, PatSource>,
label_map: FxHashMap<LabelSource, LabelId>, label_map: FxHashMap<LabelSource, LabelId>,
label_map_back: ArenaMap<LabelId, LabelSource>, label_map_back: ArenaMap<LabelId, LabelSource>,
/// We don't create explicit nodes for record fields (`S { record_field: 92 }`). /// We don't create explicit nodes for record fields (`S { record_field: 92 }`).
/// Instead, we use id of expression (`92`) to identify the field. /// Instead, we use id of expression (`92`) to identify the field.
field_map: FxHashMap<InFile<AstPtr<ast::RecordExprField>>, ExprId>, field_map: FxHashMap<FieldSource, ExprId>,
field_map_back: FxHashMap<ExprId, InFile<AstPtr<ast::RecordExprField>>>, field_map_back: FxHashMap<ExprId, FieldSource>,
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
@ -420,7 +424,7 @@ impl Index<LabelId> for Body {
// Perhaps `expr_syntax` and `expr_id`? // Perhaps `expr_syntax` and `expr_id`?
impl BodySourceMap { impl BodySourceMap {
pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> { pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> {
self.expr_map_back[expr].clone() self.expr_map_back.get(expr).cloned().ok_or(SyntheticSyntax)
} }
pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> { pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> {
@ -434,7 +438,7 @@ impl BodySourceMap {
} }
pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> { pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> {
self.pat_map_back[pat].clone() self.pat_map_back.get(pat).cloned().ok_or(SyntheticSyntax)
} }
pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> { pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> {
@ -456,9 +460,10 @@ impl BodySourceMap {
self.label_map.get(&src).cloned() self.label_map.get(&src).cloned()
} }
pub fn field_syntax(&self, expr: ExprId) -> InFile<AstPtr<ast::RecordExprField>> { pub fn field_syntax(&self, expr: ExprId) -> FieldSource {
self.field_map_back[&expr].clone() self.field_map_back[&expr].clone()
} }
pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> { pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> {
let src = node.map(AstPtr::new); let src = node.map(AstPtr::new);
self.field_map.get(&src).cloned() self.field_map.get(&src).cloned()

View file

@ -24,7 +24,7 @@ use syntax::{
use crate::{ use crate::{
adt::StructKind, adt::StructKind,
body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax}, body::{Body, BodySourceMap, Expander, ExprPtr, LabelPtr, LabelSource, PatPtr},
body::{BodyDiagnostic, ExprSource, PatSource}, body::{BodyDiagnostic, ExprSource, PatSource},
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
db::DefDatabase, db::DefDatabase,
@ -150,21 +150,21 @@ impl ExprCollector<'_> {
LowerCtx::new(self.db, self.expander.current_file_id) LowerCtx::new(self.db, self.expander.current_file_id)
} }
fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr<ast::Expr>) -> ExprId { fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId {
let src = self.expander.to_source(ptr); let src = self.expander.to_source(ptr);
let id = self.make_expr(expr, Ok(src.clone())); let id = self.make_expr(expr, src.clone());
self.source_map.expr_map.insert(src, id); self.source_map.expr_map.insert(src, id);
id id
} }
// desugared exprs don't have ptr, that's wrong and should be fixed // desugared exprs don't have ptr, that's wrong and should be fixed
// somehow. // somehow.
fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId { fn alloc_expr_desugared(&mut self, expr: Expr) -> ExprId {
self.make_expr(expr, Err(SyntheticSyntax)) self.body.exprs.alloc(expr)
} }
fn missing_expr(&mut self) -> ExprId { fn missing_expr(&mut self) -> ExprId {
self.alloc_expr_desugared(Expr::Missing) self.alloc_expr_desugared(Expr::Missing)
} }
fn make_expr(&mut self, expr: Expr, src: Result<ExprSource, SyntheticSyntax>) -> ExprId { fn make_expr(&mut self, expr: Expr, src: ExprSource) -> ExprId {
let id = self.body.exprs.alloc(expr); let id = self.body.exprs.alloc(expr);
self.source_map.expr_map_back.insert(id, src); self.source_map.expr_map_back.insert(id, src);
id id
@ -172,20 +172,20 @@ impl ExprCollector<'_> {
fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
let src = self.expander.to_source(ptr); let src = self.expander.to_source(ptr);
let id = self.make_pat(pat, Ok(src.clone())); let id = self.make_pat(pat, src.clone());
self.source_map.pat_map.insert(src, id); self.source_map.pat_map.insert(src, id);
id id
} }
fn missing_pat(&mut self) -> PatId { fn missing_pat(&mut self) -> PatId {
self.make_pat(Pat::Missing, Err(SyntheticSyntax)) self.body.pats.alloc(Pat::Missing)
} }
fn make_pat(&mut self, pat: Pat, src: Result<PatSource, SyntheticSyntax>) -> PatId { fn make_pat(&mut self, pat: Pat, src: PatSource) -> PatId {
let id = self.body.pats.alloc(pat); let id = self.body.pats.alloc(pat);
self.source_map.pat_map_back.insert(id, src); self.source_map.pat_map_back.insert(id, src);
id id
} }
fn alloc_label(&mut self, label: Label, ptr: AstPtr<ast::Label>) -> LabelId { fn alloc_label(&mut self, label: Label, ptr: LabelPtr) -> LabelId {
let src = self.expander.to_source(ptr); let src = self.expander.to_source(ptr);
let id = self.make_label(label, src.clone()); let id = self.make_label(label, src.clone());
self.source_map.label_map.insert(src, id); self.source_map.label_map.insert(src, id);
@ -550,20 +550,6 @@ impl ExprCollector<'_> {
None => self.alloc_expr(Expr::Missing, syntax_ptr), None => self.alloc_expr(Expr::Missing, syntax_ptr),
} }
} }
ast::Expr::MacroStmts(e) => {
let statements: Box<[_]> =
e.statements().filter_map(|s| self.collect_stmt(s)).collect();
let tail = e.expr().map(|e| self.collect_expr(e));
if e.syntax().children().next().is_none() {
// HACK: make sure that macros that expand to nothing aren't treated as a `()`
// expression when used in block tail position.
cov_mark::hit!(empty_macro_in_trailing_position_is_removed);
return None;
}
self.alloc_expr(Expr::MacroStmts { tail, statements }, syntax_ptr)
}
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
}) })
} }
@ -640,11 +626,46 @@ impl ExprCollector<'_> {
} }
} }
fn collect_stmt(&mut self, s: ast::Stmt) -> Option<Statement> { fn collect_macro_as_stmt(
&mut self,
statements: &mut Vec<Statement>,
mac: ast::MacroExpr,
) -> Option<ExprId> {
let mac_call = mac.macro_call()?;
let syntax_ptr = AstPtr::new(&ast::Expr::from(mac));
let macro_ptr = AstPtr::new(&mac_call);
let expansion = self.collect_macro_call(
mac_call,
macro_ptr,
false,
|this, expansion: Option<ast::MacroStmts>| match expansion {
Some(expansion) => {
expansion.statements().for_each(|stmt| this.collect_stmt(statements, stmt));
expansion.expr().and_then(|expr| match expr {
ast::Expr::MacroExpr(mac) => this.collect_macro_as_stmt(statements, mac),
expr => Some(this.collect_expr(expr)),
})
}
None => None,
},
);
match expansion {
Some(tail) => {
// Make the macro-call point to its expanded expression so we can query
// semantics on syntax pointers to the macro
let src = self.expander.to_source(syntax_ptr);
self.source_map.expr_map.insert(src, tail);
Some(tail)
}
None => None,
}
}
fn collect_stmt(&mut self, statements: &mut Vec<Statement>, s: ast::Stmt) {
match s { match s {
ast::Stmt::LetStmt(stmt) => { ast::Stmt::LetStmt(stmt) => {
if self.check_cfg(&stmt).is_none() { if self.check_cfg(&stmt).is_none() {
return None; return;
} }
let pat = self.collect_pat_opt(stmt.pat()); let pat = self.collect_pat_opt(stmt.pat());
let type_ref = let type_ref =
@ -654,61 +675,26 @@ impl ExprCollector<'_> {
.let_else() .let_else()
.and_then(|let_else| let_else.block_expr()) .and_then(|let_else| let_else.block_expr())
.map(|block| self.collect_block(block)); .map(|block| self.collect_block(block));
Some(Statement::Let { pat, type_ref, initializer, else_branch }) statements.push(Statement::Let { pat, type_ref, initializer, else_branch });
} }
ast::Stmt::ExprStmt(stmt) => { ast::Stmt::ExprStmt(stmt) => {
let expr = stmt.expr(); let expr = stmt.expr();
if let Some(expr) = &expr { match &expr {
if self.check_cfg(expr).is_none() { Some(expr) if self.check_cfg(expr).is_none() => return,
return None; _ => (),
}
} }
let has_semi = stmt.semicolon_token().is_some(); let has_semi = stmt.semicolon_token().is_some();
// Note that macro could be expanded to multiple statements // Note that macro could be expanded to multiple statements
if let Some(expr @ ast::Expr::MacroExpr(mac)) = &expr { if let Some(ast::Expr::MacroExpr(mac)) = expr {
let mac_call = mac.macro_call()?; if let Some(expr) = self.collect_macro_as_stmt(statements, mac) {
let syntax_ptr = AstPtr::new(expr); statements.push(Statement::Expr { expr, has_semi })
let macro_ptr = AstPtr::new(&mac_call);
let stmt = self.collect_macro_call(
mac_call,
macro_ptr,
false,
|this, expansion: Option<ast::MacroStmts>| match expansion {
Some(expansion) => {
let statements = expansion
.statements()
.filter_map(|stmt| this.collect_stmt(stmt))
.collect();
let tail = expansion.expr().map(|expr| this.collect_expr(expr));
let mac_stmts = this.alloc_expr(
Expr::MacroStmts { tail, statements },
AstPtr::new(&ast::Expr::MacroStmts(expansion)),
);
Some(mac_stmts)
} }
None => None,
},
);
let expr = match stmt {
Some(expr) => {
// Make the macro-call point to its expanded expression so we can query
// semantics on syntax pointers to the macro
let src = self.expander.to_source(syntax_ptr);
self.source_map.expr_map.insert(src, expr);
expr
}
None => self.alloc_expr(Expr::Missing, syntax_ptr),
};
Some(Statement::Expr { expr, has_semi })
} else { } else {
let expr = self.collect_expr_opt(expr); let expr = self.collect_expr_opt(expr);
Some(Statement::Expr { expr, has_semi }) statements.push(Statement::Expr { expr, has_semi });
} }
} }
ast::Stmt::Item(_item) => None, ast::Stmt::Item(_item) => (),
} }
} }
@ -729,9 +715,12 @@ impl ExprCollector<'_> {
let prev_def_map = mem::replace(&mut self.expander.def_map, def_map); let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
let prev_local_module = mem::replace(&mut self.expander.module, module); let prev_local_module = mem::replace(&mut self.expander.module, module);
let mut statements: Vec<_> = let mut statements = Vec::new();
block.statements().filter_map(|s| self.collect_stmt(s)).collect(); block.statements().for_each(|s| self.collect_stmt(&mut statements, s));
let tail = block.tail_expr().and_then(|e| self.maybe_collect_expr(e)); let tail = block.tail_expr().and_then(|e| match e {
ast::Expr::MacroExpr(mac) => self.collect_macro_as_stmt(&mut statements, mac),
expr => self.maybe_collect_expr(expr),
});
let tail = tail.or_else(|| { let tail = tail.or_else(|| {
let stmt = statements.pop()?; let stmt = statements.pop()?;
if let Statement::Expr { expr, has_semi: false } = stmt { if let Statement::Expr { expr, has_semi: false } = stmt {

View file

@ -422,19 +422,6 @@ impl<'a> Printer<'a> {
} }
w!(self, "}}"); w!(self, "}}");
} }
Expr::MacroStmts { statements, tail } => {
w!(self, "{{ // macro statements");
self.indented(|p| {
for stmt in statements.iter() {
p.print_stmt(stmt);
}
if let Some(tail) = tail {
p.print_expr(*tail);
}
});
self.newline();
w!(self, "}}");
}
} }
} }

View file

@ -47,16 +47,9 @@ pub struct ScopeData {
impl ExprScopes { impl ExprScopes {
pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> { pub(crate) fn expr_scopes_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<ExprScopes> {
let body = db.body(def); let body = db.body(def);
Arc::new(ExprScopes::new(&*body)) let mut scopes = ExprScopes::new(&*body);
} scopes.shrink_to_fit();
Arc::new(scopes)
fn new(body: &Body) -> ExprScopes {
let mut scopes =
ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
let mut root = scopes.root_scope();
scopes.add_params_bindings(body, root, &body.params);
compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root);
scopes
} }
pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
@ -89,6 +82,17 @@ impl ExprScopes {
pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> { pub fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
&self.scope_by_expr &self.scope_by_expr
} }
}
impl ExprScopes {
fn new(body: &Body) -> ExprScopes {
let mut scopes =
ExprScopes { scopes: Arena::default(), scope_by_expr: FxHashMap::default() };
let mut root = scopes.root_scope();
scopes.add_params_bindings(body, root, &body.params);
compute_expr_scopes(body.body_expr, body, &mut scopes, &mut root);
scopes
}
fn root_scope(&mut self) -> ScopeId { fn root_scope(&mut self) -> ScopeId {
self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] }) self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] })
@ -138,6 +142,13 @@ impl ExprScopes {
fn set_scope(&mut self, node: ExprId, scope: ScopeId) { fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
self.scope_by_expr.insert(node, scope); self.scope_by_expr.insert(node, scope);
} }
fn shrink_to_fit(&mut self) {
let ExprScopes { scopes, scope_by_expr } = self;
scopes.shrink_to_fit();
scopes.values_mut().for_each(|it| it.entries.shrink_to_fit());
scope_by_expr.shrink_to_fit();
}
} }
fn compute_block_scopes( fn compute_block_scopes(
@ -176,9 +187,6 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
scopes.set_scope(expr, *scope); scopes.set_scope(expr, *scope);
match &body[expr] { match &body[expr] {
Expr::MacroStmts { statements, tail } => {
compute_block_scopes(statements, *tail, body, scopes, scope);
}
Expr::Block { statements, tail, id, label } => { Expr::Block { statements, tail, id, label } => {
let mut scope = scopes.new_block_scope(*scope, *id, make_label(label)); let mut scope = scopes.new_block_scope(*scope, *id, make_label(label));
// Overwrite the old scope for the block expr, so that every block scope can be found // Overwrite the old scope for the block expr, so that every block scope can be found

View file

@ -206,10 +206,6 @@ pub enum Expr {
Unsafe { Unsafe {
body: ExprId, body: ExprId,
}, },
MacroStmts {
statements: Box<[Statement]>,
tail: Option<ExprId>,
},
Array(Array), Array(Array),
Literal(Literal), Literal(Literal),
Underscore, Underscore,
@ -263,7 +259,7 @@ impl Expr {
Expr::Let { expr, .. } => { Expr::Let { expr, .. } => {
f(*expr); f(*expr);
} }
Expr::MacroStmts { tail, statements } | Expr::Block { statements, tail, .. } => { Expr::Block { statements, tail, .. } => {
for stmt in statements.iter() { for stmt in statements.iter() {
match stmt { match stmt {
Statement::Let { initializer, .. } => { Statement::Let { initializer, .. } => {

View file

@ -64,7 +64,7 @@ use hir_expand::{name::Name, InFile, MacroCallId, MacroDefId};
use itertools::Itertools; use itertools::Itertools;
use la_arena::Arena; use la_arena::Arena;
use profile::Count; use profile::Count;
use rustc_hash::FxHashMap; use rustc_hash::{FxHashMap, FxHashSet};
use stdx::format_to; use stdx::format_to;
use syntax::{ast, SmolStr}; use syntax::{ast, SmolStr};
@ -98,7 +98,11 @@ pub struct DefMap {
/// The prelude module for this crate. This either comes from an import /// The prelude module for this crate. This either comes from an import
/// marked with the `prelude_import` attribute, or (in the normal case) from /// marked with the `prelude_import` attribute, or (in the normal case) from
/// a dependency (`std` or `core`). /// a dependency (`std` or `core`).
/// The prelude is empty for non-block DefMaps (unless `#[prelude_import]` was used,
/// but that attribute is nightly and when used in a block, it affects resolution globally
/// so we aren't handling this correctly anyways).
prelude: Option<ModuleId>, prelude: Option<ModuleId>,
/// The extern prelude is only populated for non-block DefMaps
extern_prelude: FxHashMap<Name, ModuleId>, extern_prelude: FxHashMap<Name, ModuleId>,
/// Side table for resolving derive helpers. /// Side table for resolving derive helpers.
@ -114,6 +118,8 @@ pub struct DefMap {
registered_attrs: Vec<SmolStr>, registered_attrs: Vec<SmolStr>,
/// Custom tool modules registered with `#![register_tool]`. /// Custom tool modules registered with `#![register_tool]`.
registered_tools: Vec<SmolStr>, registered_tools: Vec<SmolStr>,
/// Unstable features of Rust enabled with `#![feature(A, B)]`.
unstable_features: FxHashSet<SmolStr>,
edition: Edition, edition: Edition,
recursion_limit: Option<u32>, recursion_limit: Option<u32>,
@ -284,6 +290,7 @@ impl DefMap {
modules, modules,
registered_attrs: Vec::new(), registered_attrs: Vec::new(),
registered_tools: Vec::new(), registered_tools: Vec::new(),
unstable_features: FxHashSet::default(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
} }
} }
@ -314,6 +321,10 @@ impl DefMap {
&self.registered_attrs &self.registered_attrs
} }
pub fn is_unstable_feature_enabled(&self, feature: &str) -> bool {
self.unstable_features.contains(feature)
}
pub fn root(&self) -> LocalModuleId { pub fn root(&self) -> LocalModuleId {
self.root self.root
} }
@ -479,6 +490,7 @@ impl DefMap {
registered_tools, registered_tools,
fn_proc_macro_mapping, fn_proc_macro_mapping,
derive_helpers_in_scope, derive_helpers_in_scope,
unstable_features,
proc_macro_loading_error: _, proc_macro_loading_error: _,
block: _, block: _,
edition: _, edition: _,
@ -496,6 +508,7 @@ impl DefMap {
registered_tools.shrink_to_fit(); registered_tools.shrink_to_fit();
fn_proc_macro_mapping.shrink_to_fit(); fn_proc_macro_mapping.shrink_to_fit();
derive_helpers_in_scope.shrink_to_fit(); derive_helpers_in_scope.shrink_to_fit();
unstable_features.shrink_to_fit();
for (_, module) in modules.iter_mut() { for (_, module) in modules.iter_mut() {
module.children.shrink_to_fit(); module.children.shrink_to_fit();
module.scope.shrink_to_fit(); module.scope.shrink_to_fit();

View file

@ -294,6 +294,17 @@ impl DefCollector<'_> {
continue; continue;
} }
if *attr_name == hir_expand::name![feature] {
let features =
attr.parse_path_comma_token_tree().into_iter().flatten().filter_map(
|feat| match feat.segments() {
[name] => Some(name.to_smol_str()),
_ => None,
},
);
self.def_map.unstable_features.extend(features);
}
let attr_is_register_like = *attr_name == hir_expand::name![register_attr] let attr_is_register_like = *attr_name == hir_expand::name![register_attr]
|| *attr_name == hir_expand::name![register_tool]; || *attr_name == hir_expand::name![register_tool];
if !attr_is_register_like { if !attr_is_register_like {
@ -501,10 +512,9 @@ impl DefCollector<'_> {
Edition::Edition2021 => name![rust_2021], Edition::Edition2021 => name![rust_2021],
}; };
let path_kind = if self.def_map.edition == Edition::Edition2015 { let path_kind = match self.def_map.edition {
PathKind::Plain Edition::Edition2015 => PathKind::Plain,
} else { _ => PathKind::Abs,
PathKind::Abs
}; };
let path = let path =
ModPath::from_segments(path_kind, [krate.clone(), name![prelude], edition].into_iter()); ModPath::from_segments(path_kind, [krate.clone(), name![prelude], edition].into_iter());
@ -524,7 +534,6 @@ impl DefCollector<'_> {
match per_ns.types { match per_ns.types {
Some((ModuleDefId::ModuleId(m), _)) => { Some((ModuleDefId::ModuleId(m), _)) => {
self.def_map.prelude = Some(m); self.def_map.prelude = Some(m);
return;
} }
types => { types => {
tracing::debug!( tracing::debug!(
@ -839,7 +848,10 @@ impl DefCollector<'_> {
tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); tracing::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def);
// extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658
if import.is_extern_crate && module_id == self.def_map.root { if import.is_extern_crate
&& self.def_map.block.is_none()
&& module_id == self.def_map.root
{
if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name) if let (Some(ModuleDefId::ModuleId(def)), Some(name)) = (def.take_types(), name)
{ {
self.def_map.extern_prelude.insert(name.clone(), def); self.def_map.extern_prelude.insert(name.clone(), def);

View file

@ -65,6 +65,7 @@ impl ModDir {
name: &Name, name: &Name,
attr_path: Option<&SmolStr>, attr_path: Option<&SmolStr>,
) -> Result<(FileId, bool, ModDir), Box<[String]>> { ) -> Result<(FileId, bool, ModDir), Box<[String]>> {
let name = name.unescaped();
let orig_file_id = file_id.original_file(db.upcast()); let orig_file_id = file_id.original_file(db.upcast());
let mut candidate_files = ArrayVec::<_, 2>::new(); let mut candidate_files = ArrayVec::<_, 2>::new();
@ -73,12 +74,10 @@ impl ModDir {
candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner))
} }
None if file_id.is_include_macro(db.upcast()) => { None if file_id.is_include_macro(db.upcast()) => {
let name = name.unescaped();
candidate_files.push(format!("{}.rs", name)); candidate_files.push(format!("{}.rs", name));
candidate_files.push(format!("{}/mod.rs", name)); candidate_files.push(format!("{}/mod.rs", name));
} }
None => { None => {
let name = name.unescaped();
candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); candidate_files.push(format!("{}{}.rs", self.dir_path.0, name));
candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name));
} }

View file

@ -127,7 +127,15 @@ mod r#async;
use self::r#async::Bar; use self::r#async::Bar;
//- /async.rs //- /async.rs
mod foo;
mod r#async;
pub struct Bar; pub struct Bar;
//- /async/foo.rs
pub struct Foo;
//- /async/async.rs
pub struct Baz;
"#, "#,
expect![[r#" expect![[r#"
crate crate
@ -136,6 +144,14 @@ pub struct Bar;
crate::r#async crate::r#async
Bar: t v Bar: t v
foo: t
r#async: t
crate::r#async::foo
Foo: t v
crate::r#async::r#async
Baz: t v
"#]], "#]],
); );
} }

View file

@ -31,12 +31,10 @@ pub struct Resolver {
/// ///
/// When using, you generally want to process the scopes in reverse order, /// When using, you generally want to process the scopes in reverse order,
/// there's `scopes` *method* for that. /// there's `scopes` *method* for that.
///
/// Invariant: There exists at least one Scope::ModuleScope at the start of the vec.
scopes: Vec<Scope>, scopes: Vec<Scope>,
module_scope: ModuleItemMap,
} }
// FIXME how to store these best
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ModuleItemMap { struct ModuleItemMap {
def_map: Arc<DefMap>, def_map: Arc<DefMap>,
@ -53,7 +51,7 @@ struct ExprScope {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Scope { enum Scope {
/// All the items and imported names of a module /// All the items and imported names of a module
ModuleScope(ModuleItemMap), BlockScope(ModuleItemMap),
/// Brings the generic parameters of an item into scope /// Brings the generic parameters of an item into scope
GenericParams { def: GenericDefId, params: Interned<GenericParams> }, GenericParams { def: GenericDefId, params: Interned<GenericParams> },
/// Brings `Self` in `impl` block into scope /// Brings `Self` in `impl` block into scope
@ -127,24 +125,6 @@ impl Resolver {
} }
} }
fn scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.iter().rev()
}
fn resolve_module_path(
&self,
db: &dyn DefDatabase,
path: &ModPath,
shadow: BuiltinShadowMode,
) -> PerNs {
let (item_map, module) = self.module_scope();
let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow);
if segment_index.is_some() {
return PerNs::none();
}
module_res
}
pub fn resolve_module_path_in_items(&self, db: &dyn DefDatabase, path: &ModPath) -> PerNs { pub fn resolve_module_path_in_items(&self, db: &dyn DefDatabase, path: &ModPath) -> PerNs {
self.resolve_module_path(db, path, BuiltinShadowMode::Module) self.resolve_module_path(db, path, BuiltinShadowMode::Module)
} }
@ -155,7 +135,7 @@ impl Resolver {
db: &dyn DefDatabase, db: &dyn DefDatabase,
path: &ModPath, path: &ModPath,
) -> Option<PerNs> { ) -> Option<PerNs> {
let (item_map, module) = self.module_scope(); let (item_map, module) = self.item_scope();
let (module_res, idx) = item_map.resolve_path(db, module, path, BuiltinShadowMode::Module); let (module_res, idx) = item_map.resolve_path(db, module, path, BuiltinShadowMode::Module);
match module_res.take_types()? { match module_res.take_types()? {
ModuleDefId::TraitId(it) => { ModuleDefId::TraitId(it) => {
@ -183,37 +163,38 @@ impl Resolver {
) -> Option<(TypeNs, Option<usize>)> { ) -> Option<(TypeNs, Option<usize>)> {
let first_name = path.segments().first()?; let first_name = path.segments().first()?;
let skip_to_mod = path.kind != PathKind::Plain; let skip_to_mod = path.kind != PathKind::Plain;
if skip_to_mod {
return self.module_scope.resolve_path_in_type_ns(db, path);
}
let remaining_idx = || if path.segments().len() == 1 { None } else { Some(1) };
for scope in self.scopes() { for scope in self.scopes() {
match scope { match scope {
Scope::ExprScope(_) => continue, Scope::ExprScope(_) => continue,
Scope::GenericParams { .. } | Scope::ImplDefScope(_) if skip_to_mod => continue,
Scope::GenericParams { params, def } => { Scope::GenericParams { params, def } => {
if let Some(id) = params.find_type_by_name(first_name, *def) { if let Some(id) = params.find_type_by_name(first_name, *def) {
let idx = if path.segments().len() == 1 { None } else { Some(1) }; return Some((TypeNs::GenericParam(id), remaining_idx()));
return Some((TypeNs::GenericParam(id), idx));
} }
} }
Scope::ImplDefScope(impl_) => { &Scope::ImplDefScope(impl_) => {
if first_name == &name![Self] { if first_name == &name![Self] {
let idx = if path.segments().len() == 1 { None } else { Some(1) }; return Some((TypeNs::SelfType(impl_), remaining_idx()));
return Some((TypeNs::SelfType(*impl_), idx));
} }
} }
Scope::AdtScope(adt) => { &Scope::AdtScope(adt) => {
if first_name == &name![Self] { if first_name == &name![Self] {
let idx = if path.segments().len() == 1 { None } else { Some(1) }; return Some((TypeNs::AdtSelfType(adt), remaining_idx()));
return Some((TypeNs::AdtSelfType(*adt), idx));
} }
} }
Scope::ModuleScope(m) => { Scope::BlockScope(m) => {
if let Some(res) = m.resolve_path_in_type_ns(db, path) { if let Some(res) = m.resolve_path_in_type_ns(db, path) {
return Some(res); return Some(res);
} }
} }
} }
} }
None self.module_scope.resolve_path_in_type_ns(db, path)
} }
pub fn resolve_path_in_type_ns_fully( pub fn resolve_path_in_type_ns_fully(
@ -235,7 +216,7 @@ impl Resolver {
) -> Option<Visibility> { ) -> Option<Visibility> {
match visibility { match visibility {
RawVisibility::Module(_) => { RawVisibility::Module(_) => {
let (item_map, module) = self.module_scope(); let (item_map, module) = self.item_scope();
item_map.resolve_visibility(db, module, visibility) item_map.resolve_visibility(db, module, visibility)
} }
RawVisibility::Public => Some(Visibility::Public), RawVisibility::Public => Some(Visibility::Public),
@ -251,18 +232,14 @@ impl Resolver {
let tmp = name![self]; let tmp = name![self];
let first_name = if path.is_self() { &tmp } else { path.segments().first()? }; let first_name = if path.is_self() { &tmp } else { path.segments().first()? };
let skip_to_mod = path.kind != PathKind::Plain && !path.is_self(); let skip_to_mod = path.kind != PathKind::Plain && !path.is_self();
for scope in self.scopes() { if skip_to_mod {
match scope { return self.module_scope.resolve_path_in_value_ns(db, path);
Scope::AdtScope(_)
| Scope::ExprScope(_)
| Scope::GenericParams { .. }
| Scope::ImplDefScope(_)
if skip_to_mod =>
{
continue
} }
Scope::ExprScope(scope) if n_segments <= 1 => { for scope in self.scopes() {
match scope {
Scope::ExprScope(_) if n_segments > 1 => continue,
Scope::ExprScope(scope) => {
let entry = scope let entry = scope
.expr_scopes .expr_scopes
.entries(scope.scope_id) .entries(scope.scope_id)
@ -273,44 +250,39 @@ impl Resolver {
return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat()))); return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat())));
} }
} }
Scope::ExprScope(_) => continue,
Scope::GenericParams { params, def } if n_segments > 1 => { Scope::GenericParams { params, def } if n_segments > 1 => {
if let Some(id) = params.find_type_by_name(first_name, *def) { if let Some(id) = params.find_type_by_name(first_name, *def) {
let ty = TypeNs::GenericParam(id); let ty = TypeNs::GenericParam(id);
return Some(ResolveValueResult::Partial(ty, 1)); return Some(ResolveValueResult::Partial(ty, 1));
} }
} }
Scope::GenericParams { params, def } if n_segments == 1 => { Scope::GenericParams { .. } if n_segments != 1 => continue,
Scope::GenericParams { params, def } => {
if let Some(id) = params.find_const_by_name(first_name, *def) { if let Some(id) = params.find_const_by_name(first_name, *def) {
let val = ValueNs::GenericParam(id); let val = ValueNs::GenericParam(id);
return Some(ResolveValueResult::ValueNs(val)); return Some(ResolveValueResult::ValueNs(val));
} }
} }
Scope::GenericParams { .. } => continue,
Scope::ImplDefScope(impl_) => { &Scope::ImplDefScope(impl_) => {
if first_name == &name![Self] { if first_name == &name![Self] {
if n_segments > 1 { return Some(if n_segments > 1 {
let ty = TypeNs::SelfType(*impl_); ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1)
return Some(ResolveValueResult::Partial(ty, 1));
} else { } else {
return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(*impl_))); ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_))
});
} }
} }
}
Scope::AdtScope(adt) => {
if n_segments == 1 {
// bare `Self` doesn't work in the value namespace in a struct/enum definition // bare `Self` doesn't work in the value namespace in a struct/enum definition
continue; Scope::AdtScope(_) if n_segments == 1 => continue,
} Scope::AdtScope(adt) => {
if first_name == &name![Self] { if first_name == &name![Self] {
let ty = TypeNs::AdtSelfType(*adt); let ty = TypeNs::AdtSelfType(*adt);
return Some(ResolveValueResult::Partial(ty, 1)); return Some(ResolveValueResult::Partial(ty, 1));
} }
} }
Scope::ModuleScope(m) => { Scope::BlockScope(m) => {
if let Some(def) = m.resolve_path_in_value_ns(db, path) { if let Some(def) = m.resolve_path_in_value_ns(db, path) {
return Some(def); return Some(def);
} }
@ -318,16 +290,17 @@ impl Resolver {
} }
} }
if let res @ Some(_) = self.module_scope.resolve_path_in_value_ns(db, path) {
return res;
}
// If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back
// to resolving to the primitive type, to allow this to still work in the presence of // to resolving to the primitive type, to allow this to still work in the presence of
// `use core::u16;`. // `use core::u16;`.
if path.kind == PathKind::Plain && path.segments().len() > 1 { if path.kind == PathKind::Plain && path.segments().len() > 1 {
match BuiltinType::by_name(&path.segments()[0]) { if let Some(builtin) = BuiltinType::by_name(&path.segments()[0]) {
Some(builtin) => {
return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1)); return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1));
} }
None => {}
}
} }
None None
@ -345,7 +318,7 @@ impl Resolver {
} }
pub fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> { pub fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> {
let (item_map, module) = self.module_scope(); let (item_map, module) = self.item_scope();
item_map.resolve_path(db, module, path, BuiltinShadowMode::Other).0.take_macros() item_map.resolve_path(db, module, path, BuiltinShadowMode::Other).0.take_macros()
} }
@ -395,30 +368,43 @@ impl Resolver {
for scope in self.scopes() { for scope in self.scopes() {
scope.process_names(&mut res, db); scope.process_names(&mut res, db);
} }
let ModuleItemMap { ref def_map, module_id } = self.module_scope;
// FIXME: should we provide `self` here?
// f(
// Name::self_param(),
// PerNs::types(Resolution::Def {
// def: m.module.into(),
// }),
// );
def_map[module_id].scope.entries().for_each(|(name, def)| {
res.add_per_ns(name, def);
});
def_map[module_id].scope.legacy_macros().for_each(|(name, macs)| {
macs.iter().for_each(|&mac| {
res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(MacroId::from(mac))));
})
});
def_map.extern_prelude().for_each(|(name, &def)| {
res.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def)));
});
BUILTIN_SCOPE.iter().for_each(|(name, &def)| {
res.add_per_ns(name, def);
});
if let Some(prelude) = def_map.prelude() {
let prelude_def_map = prelude.def_map(db);
for (name, def) in prelude_def_map[prelude.local_id].scope.entries() {
res.add_per_ns(name, def)
}
}
res.map res.map
} }
pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
let mut traits = FxHashSet::default(); let mut traits = FxHashSet::default();
for scope in self.scopes() { for scope in self.scopes() {
match scope { match scope {
Scope::ModuleScope(m) => { Scope::BlockScope(m) => traits.extend(m.def_map[m.module_id].scope.traits()),
if let Some(prelude) = m.def_map.prelude() {
let prelude_def_map = prelude.def_map(db);
traits.extend(prelude_def_map[prelude.local_id].scope.traits());
}
traits.extend(m.def_map[m.module_id].scope.traits());
// Add all traits that are in scope because of the containing DefMaps
m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
if let Some(prelude) = def_map.prelude() {
let prelude_def_map = prelude.def_map(db);
traits.extend(prelude_def_map[prelude.local_id].scope.traits());
}
traits.extend(def_map[module].scope.traits());
None::<()>
});
}
&Scope::ImplDefScope(impl_) => { &Scope::ImplDefScope(impl_) => {
if let Some(target_trait) = &db.impl_data(impl_).target_trait { if let Some(target_trait) = &db.impl_data(impl_).target_trait {
if let Some(TypeNs::TraitId(trait_)) = if let Some(TypeNs::TraitId(trait_)) =
@ -431,35 +417,28 @@ impl Resolver {
_ => (), _ => (),
} }
} }
// Fill in the prelude traits
if let Some(prelude) = self.module_scope.def_map.prelude() {
let prelude_def_map = prelude.def_map(db);
traits.extend(prelude_def_map[prelude.local_id].scope.traits());
}
// Fill in module visible traits
traits.extend(self.module_scope.def_map[self.module_scope.module_id].scope.traits());
traits traits
} }
fn module_scope(&self) -> (&DefMap, LocalModuleId) {
self.scopes()
.find_map(|scope| match scope {
Scope::ModuleScope(m) => Some((&*m.def_map, m.module_id)),
_ => None,
})
.expect("module scope invariant violated")
}
pub fn module(&self) -> ModuleId { pub fn module(&self) -> ModuleId {
let (def_map, local_id) = self.module_scope(); let (def_map, local_id) = self.item_scope();
def_map.module_id(local_id) def_map.module_id(local_id)
} }
pub fn krate(&self) -> CrateId { pub fn krate(&self) -> CrateId {
self.def_map().krate() self.module_scope.def_map.krate()
} }
pub fn def_map(&self) -> &DefMap { pub fn def_map(&self) -> &DefMap {
self.scopes self.item_scope().0
.get(0)
.and_then(|scope| match scope {
Scope::ModuleScope(m) => Some(&m.def_map),
_ => None,
})
.expect("module scope invariant violated")
} }
pub fn where_predicates_in_scope( pub fn where_predicates_in_scope(
@ -488,6 +467,36 @@ impl Resolver {
} }
} }
impl Resolver {
fn scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.iter().rev()
}
fn resolve_module_path(
&self,
db: &dyn DefDatabase,
path: &ModPath,
shadow: BuiltinShadowMode,
) -> PerNs {
let (item_map, module) = self.item_scope();
let (module_res, segment_index) = item_map.resolve_path(db, module, path, shadow);
if segment_index.is_some() {
return PerNs::none();
}
module_res
}
/// The innermost block scope that contains items or the module scope that contains this resolver.
fn item_scope(&self) -> (&DefMap, LocalModuleId) {
self.scopes()
.find_map(|scope| match scope {
Scope::BlockScope(m) => Some((&*m.def_map, m.module_id)),
_ => None,
})
.unwrap_or((&self.module_scope.def_map, self.module_scope.module_id))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ScopeDef { pub enum ScopeDef {
ModuleDef(ModuleDefId), ModuleDef(ModuleDefId),
@ -502,14 +511,7 @@ pub enum ScopeDef {
impl Scope { impl Scope {
fn process_names(&self, acc: &mut ScopeNames, db: &dyn DefDatabase) { fn process_names(&self, acc: &mut ScopeNames, db: &dyn DefDatabase) {
match self { match self {
Scope::ModuleScope(m) => { Scope::BlockScope(m) => {
// FIXME: should we provide `self` here?
// f(
// Name::self_param(),
// PerNs::types(Resolution::Def {
// def: m.module.into(),
// }),
// );
m.def_map[m.module_id].scope.entries().for_each(|(name, def)| { m.def_map[m.module_id].scope.entries().for_each(|(name, def)| {
acc.add_per_ns(name, def); acc.add_per_ns(name, def);
}); });
@ -521,18 +523,6 @@ impl Scope {
); );
}) })
}); });
m.def_map.extern_prelude().for_each(|(name, &def)| {
acc.add(name, ScopeDef::ModuleDef(ModuleDefId::ModuleId(def)));
});
BUILTIN_SCOPE.iter().for_each(|(name, &def)| {
acc.add_per_ns(name, def);
});
if let Some(prelude) = m.def_map.prelude() {
let prelude_def_map = prelude.def_map(db);
for (name, def) in prelude_def_map[prelude.local_id].scope.entries() {
acc.add_per_ns(name, def)
}
}
} }
Scope::GenericParams { params, def: parent } => { Scope::GenericParams { params, def: parent } => {
let parent = *parent; let parent = *parent;
@ -596,7 +586,7 @@ pub fn resolver_for_scope(
if let Some(block) = scopes.block(scope) { if let Some(block) = scopes.block(scope) {
if let Some(def_map) = db.block_def_map(block) { if let Some(def_map) = db.block_def_map(block) {
let root = def_map.root(); let root = def_map.root();
r = r.push_module_scope(def_map, root); r = r.push_block_scope(def_map, root);
// FIXME: This adds as many module scopes as there are blocks, but resolving in each // FIXME: This adds as many module scopes as there are blocks, but resolving in each
// already traverses all parents, so this is O(n²). I think we could only store the // already traverses all parents, so this is O(n²). I think we could only store the
// innermost module scope instead? // innermost module scope instead?
@ -623,8 +613,8 @@ impl Resolver {
self.push_scope(Scope::ImplDefScope(impl_def)) self.push_scope(Scope::ImplDefScope(impl_def))
} }
fn push_module_scope(self, def_map: Arc<DefMap>, module_id: LocalModuleId) -> Resolver { fn push_block_scope(self, def_map: Arc<DefMap>, module_id: LocalModuleId) -> Resolver {
self.push_scope(Scope::ModuleScope(ModuleItemMap { def_map, module_id })) self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, module_id }))
} }
fn push_expr_scope( fn push_expr_scope(
@ -768,14 +758,19 @@ pub trait HasResolver: Copy {
impl HasResolver for ModuleId { impl HasResolver for ModuleId {
fn resolver(self, db: &dyn DefDatabase) -> Resolver { fn resolver(self, db: &dyn DefDatabase) -> Resolver {
let mut def_map = self.def_map(db); let mut def_map = self.def_map(db);
let mut modules: SmallVec<[_; 2]> = smallvec![(def_map.clone(), self.local_id)]; let mut modules: SmallVec<[_; 1]> = smallvec![];
let mut module_id = self.local_id;
while let Some(parent) = def_map.parent() { while let Some(parent) = def_map.parent() {
modules.push((def_map, module_id));
def_map = parent.def_map(db); def_map = parent.def_map(db);
modules.push((def_map.clone(), parent.local_id)); module_id = parent.local_id;
} }
let mut resolver = Resolver { scopes: Vec::with_capacity(modules.len()) }; let mut resolver = Resolver {
scopes: Vec::with_capacity(modules.len()),
module_scope: ModuleItemMap { def_map, module_id },
};
for (def_map, module) in modules.into_iter().rev() { for (def_map, module) in modules.into_iter().rev() {
resolver = resolver.push_module_scope(def_map, module); resolver = resolver.push_block_scope(def_map, module);
} }
resolver resolver
} }

View file

@ -969,7 +969,7 @@ impl ExpandTo {
if parent.kind() == MACRO_EXPR if parent.kind() == MACRO_EXPR
&& parent && parent
.parent() .parent()
.map_or(true, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS)) .map_or(false, |p| matches!(p.kind(), EXPR_STMT | STMT_LIST | MACRO_STMTS))
{ {
return ExpandTo::Statements; return ExpandTo::Statements;
} }

View file

@ -336,6 +336,7 @@ pub mod known {
test, test,
test_case, test_case,
recursion_limit, recursion_limit,
feature,
// Safe intrinsics // Safe intrinsics
abort, abort,
add_with_overflow, add_with_overflow,

View file

@ -104,8 +104,7 @@ pub(crate) fn deref(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> {
fn builtin_deref(ty: &Ty) -> Option<&Ty> { fn builtin_deref(ty: &Ty) -> Option<&Ty> {
match ty.kind(Interner) { match ty.kind(Interner) {
TyKind::Ref(.., ty) => Some(ty), TyKind::Ref(.., ty) | TyKind::Raw(.., ty) => Some(ty),
TyKind::Raw(.., ty) => Some(ty),
_ => None, _ => None,
} }
} }

View file

@ -159,12 +159,7 @@ impl ExprValidator {
} }
let pattern_arena = Arena::new(); let pattern_arena = Arena::new();
let cx = MatchCheckCtx { let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
module: self.owner.module(db.upcast()),
body: self.owner,
db,
pattern_arena: &pattern_arena,
};
let mut m_arms = Vec::with_capacity(arms.len()); let mut m_arms = Vec::with_capacity(arms.len());
let mut has_lowering_errors = false; let mut has_lowering_errors = false;

View file

@ -52,7 +52,10 @@ use hir_def::{EnumVariantId, HasModule, LocalFieldId, VariantId};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use stdx::never; use stdx::never;
use crate::{infer::normalize, AdtId, Interner, Scalar, Ty, TyExt, TyKind}; use crate::{
infer::normalize, inhabitedness::is_enum_variant_uninhabited_from, AdtId, Interner, Scalar, Ty,
TyExt, TyKind,
};
use super::{ use super::{
is_box, is_box,
@ -557,8 +560,8 @@ impl SplitWildcard {
TyKind::Scalar(Scalar::Bool) => smallvec![make_range(0, 1, Scalar::Bool)], TyKind::Scalar(Scalar::Bool) => smallvec![make_range(0, 1, Scalar::Bool)],
// TyKind::Array(..) if ... => unhandled(), // TyKind::Array(..) if ... => unhandled(),
TyKind::Array(..) | TyKind::Slice(..) => unhandled(), TyKind::Array(..) | TyKind::Slice(..) => unhandled(),
&TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), ..) => { TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), subst) => {
let enum_data = cx.db.enum_data(enum_id); let enum_data = cx.db.enum_data(*enum_id);
// If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an
// additional "unknown" constructor. // additional "unknown" constructor.
@ -591,14 +594,15 @@ impl SplitWildcard {
let mut ctors: SmallVec<[_; 1]> = enum_data let mut ctors: SmallVec<[_; 1]> = enum_data
.variants .variants
.iter() .iter()
.filter(|&(_, _v)| { .map(|(local_id, _)| EnumVariantId { parent: *enum_id, local_id })
.filter(|&variant| {
// If `exhaustive_patterns` is enabled, we exclude variants known to be // If `exhaustive_patterns` is enabled, we exclude variants known to be
// uninhabited. // uninhabited.
let is_uninhabited = is_exhaustive_pat_feature let is_uninhabited = is_exhaustive_pat_feature
&& unimplemented!("after MatchCheckCtx.feature_exhaustive_patterns()"); && is_enum_variant_uninhabited_from(variant, subst, cx.module, cx.db);
!is_uninhabited !is_uninhabited
}) })
.map(|(local_id, _)| Variant(EnumVariantId { parent: enum_id, local_id })) .map(Variant)
.collect(); .collect();
if is_secretly_empty || is_declared_nonexhaustive { if is_secretly_empty || is_declared_nonexhaustive {

View file

@ -277,7 +277,7 @@ use hir_def::{AdtId, DefWithBodyId, HasModule, ModuleId};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use typed_arena::Arena; use typed_arena::Arena;
use crate::{db::HirDatabase, Ty, TyExt}; use crate::{db::HirDatabase, inhabitedness::is_ty_uninhabited_from, Ty, TyExt};
use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard};
@ -289,14 +289,28 @@ pub(crate) struct MatchCheckCtx<'a, 'p> {
pub(crate) db: &'a dyn HirDatabase, pub(crate) db: &'a dyn HirDatabase,
/// Lowered patterns from arms plus generated by the check. /// Lowered patterns from arms plus generated by the check.
pub(crate) pattern_arena: &'p Arena<DeconstructedPat<'p>>, pub(crate) pattern_arena: &'p Arena<DeconstructedPat<'p>>,
exhaustive_patterns: bool,
} }
impl<'a, 'p> MatchCheckCtx<'a, 'p> { impl<'a, 'p> MatchCheckCtx<'a, 'p> {
pub(super) fn is_uninhabited(&self, _ty: &Ty) -> bool { pub(crate) fn new(
// FIXME(iDawer) implement exhaustive_patterns feature. More info in: module: ModuleId,
// Tracking issue for RFC 1872: exhaustive_patterns feature https://github.com/rust-lang/rust/issues/51085 body: DefWithBodyId,
db: &'a dyn HirDatabase,
pattern_arena: &'p Arena<DeconstructedPat<'p>>,
) -> Self {
let def_map = db.crate_def_map(module.krate());
let exhaustive_patterns = def_map.is_unstable_feature_enabled("exhaustive_patterns");
Self { module, body, db, pattern_arena, exhaustive_patterns }
}
pub(super) fn is_uninhabited(&self, ty: &Ty) -> bool {
if self.feature_exhaustive_patterns() {
is_ty_uninhabited_from(ty, self.module, self.db)
} else {
false false
} }
}
/// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`. /// Returns whether the given type is an enum from another crate declared `#[non_exhaustive]`.
pub(super) fn is_foreign_non_exhaustive_enum(&self, ty: &Ty) -> bool { pub(super) fn is_foreign_non_exhaustive_enum(&self, ty: &Ty) -> bool {
@ -311,10 +325,9 @@ impl<'a, 'p> MatchCheckCtx<'a, 'p> {
} }
} }
// Rust feature described as "Allows exhaustive pattern matching on types that contain uninhabited types." // Rust's unstable feature described as "Allows exhaustive pattern matching on types that contain uninhabited types."
pub(super) fn feature_exhaustive_patterns(&self) -> bool { pub(super) fn feature_exhaustive_patterns(&self) -> bool {
// FIXME see MatchCheckCtx::is_uninhabited self.exhaustive_patterns
false
} }
} }

View file

@ -182,7 +182,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum InferenceDiagnostic { pub enum InferenceDiagnostic {
NoSuchField { expr: ExprId }, NoSuchField { expr: ExprId },
BreakOutsideOfLoop { expr: ExprId }, BreakOutsideOfLoop { expr: ExprId, is_break: bool },
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
} }
@ -418,18 +418,45 @@ pub(crate) struct InferenceContext<'a> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct BreakableContext { struct BreakableContext {
/// Whether this context contains at least one break expression.
may_break: bool, may_break: bool,
/// The coercion target of the context.
coerce: CoerceMany, coerce: CoerceMany,
/// The optional label of the context.
label: Option<name::Name>, label: Option<name::Name>,
kind: BreakableKind,
}
#[derive(Clone, Debug)]
enum BreakableKind {
Block,
Loop,
/// A border is something like an async block, closure etc. Anything that prevents
/// breaking/continuing through
Border,
} }
fn find_breakable<'c>( fn find_breakable<'c>(
ctxs: &'c mut [BreakableContext], ctxs: &'c mut [BreakableContext],
label: Option<&name::Name>, label: Option<&name::Name>,
) -> Option<&'c mut BreakableContext> { ) -> Option<&'c mut BreakableContext> {
let mut ctxs = ctxs
.iter_mut()
.rev()
.take_while(|it| matches!(it.kind, BreakableKind::Block | BreakableKind::Loop));
match label { match label {
Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), Some(_) => ctxs.find(|ctx| ctx.label.as_ref() == label),
None => ctxs.last_mut(), None => ctxs.find(|ctx| matches!(ctx.kind, BreakableKind::Loop)),
}
}
fn find_continuable<'c>(
ctxs: &'c mut [BreakableContext],
label: Option<&name::Name>,
) -> Option<&'c mut BreakableContext> {
match label {
Some(_) => find_breakable(ctxs, label).filter(|it| matches!(it.kind, BreakableKind::Loop)),
None => find_breakable(ctxs, label),
} }
} }

View file

@ -10,7 +10,7 @@ use chalk_ir::{
cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind, cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind,
}; };
use hir_def::{ use hir_def::{
expr::{ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, Literal, Statement, UnaryOp}, expr::{ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, LabelId, Literal, Statement, UnaryOp},
generics::TypeOrConstParamData, generics::TypeOrConstParamData,
path::{GenericArg, GenericArgs}, path::{GenericArg, GenericArgs},
resolver::resolver_for_expr, resolver::resolver_for_expr,
@ -23,7 +23,7 @@ use syntax::ast::RangeOp;
use crate::{ use crate::{
autoderef::{self, Autoderef}, autoderef::{self, Autoderef},
consteval, consteval,
infer::coerce::CoerceMany, infer::{coerce::CoerceMany, find_continuable, BreakableKind},
lower::{ lower::{
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
}, },
@ -120,32 +120,37 @@ impl<'a> InferenceContext<'a> {
let ty = match label { let ty = match label {
Some(_) => { Some(_) => {
let break_ty = self.table.new_type_var(); let break_ty = self.table.new_type_var();
self.breakables.push(BreakableContext { let (breaks, ty) = self.with_breakable_ctx(
may_break: false, BreakableKind::Block,
coerce: CoerceMany::new(break_ty.clone()), break_ty.clone(),
label: label.map(|label| self.body[label].name.clone()), *label,
}); |this| {
let ty = self.infer_block( this.infer_block(
tgt_expr, tgt_expr,
statements, statements,
*tail, *tail,
&Expectation::has_type(break_ty), &Expectation::has_type(break_ty),
)
},
); );
let ctxt = self.breakables.pop().expect("breakable stack broken"); breaks.unwrap_or(ty)
if ctxt.may_break {
ctxt.coerce.complete()
} else {
ty
}
} }
None => self.infer_block(tgt_expr, statements, *tail, expected), None => self.infer_block(tgt_expr, statements, *tail, expected),
}; };
self.resolver = old_resolver; self.resolver = old_resolver;
ty ty
} }
Expr::Unsafe { body } | Expr::Const { body } => self.infer_expr(*body, expected), Expr::Unsafe { body } => self.infer_expr(*body, expected),
Expr::Const { body } => {
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr(*body, expected)
})
.1
}
Expr::TryBlock { body } => { Expr::TryBlock { body } => {
let _inner = self.infer_expr(*body, expected); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
let _inner = this.infer_expr(*body, expected);
});
// FIXME should be std::result::Result<{inner}, _> // FIXME should be std::result::Result<{inner}, _>
self.err_ty() self.err_ty()
} }
@ -154,7 +159,10 @@ impl<'a> InferenceContext<'a> {
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
let inner_ty = self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); let (_, inner_ty) =
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
});
self.diverges = prev_diverges; self.diverges = prev_diverges;
self.return_ty = prev_ret_ty; self.return_ty = prev_ret_ty;
@ -166,54 +174,44 @@ impl<'a> InferenceContext<'a> {
TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty)) TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty))
.intern(Interner) .intern(Interner)
} }
Expr::Loop { body, label } => { &Expr::Loop { body, label } => {
self.breakables.push(BreakableContext { let ty = self.table.new_type_var();
may_break: false, let (breaks, ()) =
coerce: CoerceMany::new(self.table.new_type_var()), self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| {
label: label.map(|label| self.body[label].name.clone()), this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
}); });
self.infer_expr(*body, &Expectation::has_type(TyBuilder::unit()));
let ctxt = self.breakables.pop().expect("breakable stack broken"); match breaks {
Some(breaks) => {
if ctxt.may_break {
self.diverges = Diverges::Maybe; self.diverges = Diverges::Maybe;
ctxt.coerce.complete() breaks
} else { }
TyKind::Never.intern(Interner) None => TyKind::Never.intern(Interner),
} }
} }
Expr::While { condition, body, label } => { &Expr::While { condition, body, label } => {
self.breakables.push(BreakableContext { self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
may_break: false, this.infer_expr(
coerce: CoerceMany::new(self.err_ty()), condition,
label: label.map(|label| self.body[label].name.clone()),
});
self.infer_expr(
*condition,
&Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)), &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
); );
self.infer_expr(*body, &Expectation::has_type(TyBuilder::unit())); this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
let _ctxt = self.breakables.pop().expect("breakable stack broken"); });
// the body may not run, so it diverging doesn't mean we diverge // the body may not run, so it diverging doesn't mean we diverge
self.diverges = Diverges::Maybe; self.diverges = Diverges::Maybe;
TyBuilder::unit() TyBuilder::unit()
} }
Expr::For { iterable, body, pat, label } => { &Expr::For { iterable, body, pat, label } => {
let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); let iterable_ty = self.infer_expr(iterable, &Expectation::none());
self.breakables.push(BreakableContext {
may_break: false,
coerce: CoerceMany::new(self.err_ty()),
label: label.map(|label| self.body[label].name.clone()),
});
let pat_ty = let pat_ty =
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
self.infer_pat(*pat, &pat_ty, BindingMode::default()); self.infer_pat(pat, &pat_ty, BindingMode::default());
self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
});
self.infer_expr(*body, &Expectation::has_type(TyBuilder::unit()));
let _ctxt = self.breakables.pop().expect("breakable stack broken");
// the body may not run, so it diverging doesn't mean we diverge // the body may not run, so it diverging doesn't mean we diverge
self.diverges = Diverges::Maybe; self.diverges = Diverges::Maybe;
TyBuilder::unit() TyBuilder::unit()
@ -269,7 +267,9 @@ impl<'a> InferenceContext<'a> {
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
});
self.diverges = prev_diverges; self.diverges = prev_diverges;
self.return_ty = prev_ret_ty; self.return_ty = prev_ret_ty;
@ -372,37 +372,45 @@ impl<'a> InferenceContext<'a> {
let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()) self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty())
} }
Expr::Continue { .. } => TyKind::Never.intern(Interner), Expr::Continue { label } => {
Expr::Break { expr, label } => { if let None = find_continuable(&mut self.breakables, label.as_ref()) {
let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
Some(ctxt) => { expr: tgt_expr,
// avoiding the borrowck is_break: false,
mem::replace( });
&mut ctxt.coerce,
CoerceMany::new(self.result.standard_types.unknown.clone()),
)
}
None => CoerceMany::new(self.result.standard_types.unknown.clone()),
}; };
TyKind::Never.intern(Interner)
}
Expr::Break { expr, label } => {
let val_ty = if let Some(expr) = *expr { let val_ty = if let Some(expr) = *expr {
self.infer_expr(expr, &Expectation::none()) self.infer_expr(expr, &Expectation::none())
} else { } else {
TyBuilder::unit() TyBuilder::unit()
}; };
match find_breakable(&mut self.breakables, label.as_ref()) {
Some(ctxt) => {
// avoiding the borrowck
let mut coerce = mem::replace(
&mut ctxt.coerce,
CoerceMany::new(self.result.standard_types.unknown.clone()),
);
// FIXME: create a synthetic `()` during lowering so we have something to refer to here? // FIXME: create a synthetic `()` during lowering so we have something to refer to here?
coerce.coerce(self, *expr, &val_ty); coerce.coerce(self, *expr, &val_ty);
if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { let ctxt = find_breakable(&mut self.breakables, label.as_ref())
.expect("breakable stack changed during coercion");
ctxt.coerce = coerce; ctxt.coerce = coerce;
ctxt.may_break = true; ctxt.may_break = true;
} else { }
None => {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
is_break: true,
}); });
}; }
}
TyKind::Never.intern(Interner) TyKind::Never.intern(Interner)
} }
Expr::Return { expr } => { Expr::Return { expr } => {
@ -794,9 +802,6 @@ impl<'a> InferenceContext<'a> {
None => self.table.new_float_var(), None => self.table.new_float_var(),
}, },
}, },
Expr::MacroStmts { tail, statements } => {
self.infer_block(tgt_expr, statements, *tail, expected)
}
Expr::Underscore => { Expr::Underscore => {
// Underscore expressions may only appear in assignee expressions, // Underscore expressions may only appear in assignee expressions,
// which are handled by `infer_assignee_expr()`, so any underscore // which are handled by `infer_assignee_expr()`, so any underscore
@ -1475,4 +1480,20 @@ impl<'a> InferenceContext<'a> {
}, },
}) })
} }
fn with_breakable_ctx<T>(
&mut self,
kind: BreakableKind,
ty: Ty,
label: Option<LabelId>,
cb: impl FnOnce(&mut Self) -> T,
) -> (Option<Ty>, T) {
self.breakables.push({
let label = label.map(|label| self.body[label].name.clone());
BreakableContext { kind, may_break: false, coerce: CoerceMany::new(ty), label }
});
let res = cb(self);
let ctx = self.breakables.pop().expect("breakable stack broken");
(ctx.may_break.then(|| ctx.coerce.complete()), res)
}
} }

View file

@ -0,0 +1,173 @@
//! Type inhabitedness logic.
use std::ops::ControlFlow::{self, Break, Continue};
use chalk_ir::{
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
DebruijnIndex,
};
use hir_def::{
adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId,
EnumVariantId, HasModule, Lookup, ModuleId, VariantId,
};
use crate::{
db::HirDatabase, Binders, ConcreteConst, Const, ConstValue, Interner, Substitution, Ty, TyKind,
};
/// Checks whether a type is visibly uninhabited from a particular module.
pub(crate) fn is_ty_uninhabited_from(ty: &Ty, target_mod: ModuleId, db: &dyn HirDatabase) -> bool {
let mut uninhabited_from = UninhabitedFrom { target_mod, db };
let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
/// Checks whether a variant is visibly uninhabited from a particular module.
pub(crate) fn is_enum_variant_uninhabited_from(
variant: EnumVariantId,
subst: &Substitution,
target_mod: ModuleId,
db: &dyn HirDatabase,
) -> bool {
let enum_data = db.enum_data(variant.parent);
let vars_attrs = db.variants_attrs(variant.parent);
let is_local = variant.parent.lookup(db.upcast()).container.krate() == target_mod.krate();
let mut uninhabited_from = UninhabitedFrom { target_mod, db };
let inhabitedness = uninhabited_from.visit_variant(
variant.into(),
&enum_data.variants[variant.local_id].variant_data,
subst,
&vars_attrs[variant.local_id],
is_local,
);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
struct UninhabitedFrom<'a> {
target_mod: ModuleId,
db: &'a dyn HirDatabase,
}
const CONTINUE_OPAQUELY_INHABITED: ControlFlow<VisiblyUninhabited> = Continue(());
const BREAK_VISIBLY_UNINHABITED: ControlFlow<VisiblyUninhabited> = Break(VisiblyUninhabited);
#[derive(PartialEq, Eq)]
struct VisiblyUninhabited;
impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
type BreakTy = VisiblyUninhabited;
fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = VisiblyUninhabited> {
self
}
fn visit_ty(
&mut self,
ty: &Ty,
outer_binder: DebruijnIndex,
) -> ControlFlow<VisiblyUninhabited> {
match ty.kind(Interner) {
TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst),
TyKind::Never => BREAK_VISIBLY_UNINHABITED,
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder),
TyKind::Array(item_ty, len) => match try_usize_const(len) {
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
Some(1..) => item_ty.super_visit_with(self, outer_binder),
},
TyKind::Ref(..) | _ => CONTINUE_OPAQUELY_INHABITED,
}
}
fn interner(&self) -> Interner {
Interner
}
}
impl UninhabitedFrom<'_> {
fn visit_adt(&mut self, adt: AdtId, subst: &Substitution) -> ControlFlow<VisiblyUninhabited> {
let attrs = self.db.attrs(adt.into());
let adt_non_exhaustive = attrs.by_key("non_exhaustive").exists();
let is_local = adt.module(self.db.upcast()).krate() == self.target_mod.krate();
if adt_non_exhaustive && !is_local {
return CONTINUE_OPAQUELY_INHABITED;
}
// An ADT is uninhabited iff all its variants uninhabited.
match adt {
// rustc: For now, `union`s are never considered uninhabited.
AdtId::UnionId(_) => CONTINUE_OPAQUELY_INHABITED,
AdtId::StructId(s) => {
let struct_data = self.db.struct_data(s);
self.visit_variant(s.into(), &struct_data.variant_data, subst, &attrs, is_local)
}
AdtId::EnumId(e) => {
let vars_attrs = self.db.variants_attrs(e);
let enum_data = self.db.enum_data(e);
for (local_id, enum_var) in enum_data.variants.iter() {
let variant_inhabitedness = self.visit_variant(
EnumVariantId { parent: e, local_id }.into(),
&enum_var.variant_data,
subst,
&vars_attrs[local_id],
is_local,
);
match variant_inhabitedness {
Break(VisiblyUninhabited) => continue,
Continue(()) => return CONTINUE_OPAQUELY_INHABITED,
}
}
BREAK_VISIBLY_UNINHABITED
}
}
}
fn visit_variant(
&mut self,
variant: VariantId,
variant_data: &VariantData,
subst: &Substitution,
attrs: &Attrs,
is_local: bool,
) -> ControlFlow<VisiblyUninhabited> {
let non_exhaustive_field_list = attrs.by_key("non_exhaustive").exists();
if non_exhaustive_field_list && !is_local {
return CONTINUE_OPAQUELY_INHABITED;
}
let is_enum = matches!(variant, VariantId::EnumVariantId(..));
let field_tys = self.db.field_types(variant);
let field_vis = self.db.field_visibilities(variant);
for (fid, _) in variant_data.fields().iter() {
self.visit_field(field_vis[fid], &field_tys[fid], subst, is_enum)?;
}
CONTINUE_OPAQUELY_INHABITED
}
fn visit_field(
&mut self,
vis: Visibility,
ty: &Binders<Ty>,
subst: &Substitution,
is_enum: bool,
) -> ControlFlow<VisiblyUninhabited> {
if is_enum || vis.is_visible_from(self.db.upcast(), self.target_mod) {
let ty = ty.clone().substitute(Interner, subst);
ty.visit_with(self, DebruijnIndex::INNERMOST)
} else {
CONTINUE_OPAQUELY_INHABITED
}
}
}
fn try_usize_const(c: &Const) -> Option<u128> {
let data = &c.data(Interner);
if data.ty.kind(Interner) != &TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)) {
return None;
}
match data.value {
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(value) }) => Some(value),
_ => None,
}
}

View file

@ -14,6 +14,7 @@ mod chalk_db;
mod chalk_ext; mod chalk_ext;
pub mod consteval; pub mod consteval;
mod infer; mod infer;
mod inhabitedness;
mod interner; mod interner;
mod lower; mod lower;
mod mapping; mod mapping;

View file

@ -1,8 +1,8 @@
//! Methods for lowering the HIR to types. There are two main cases here: //! Methods for lowering the HIR to types. There are two main cases here:
//! //!
//! - Lowering a type reference like `&usize` or `Option<foo::bar::Baz>` to a //! - Lowering a type reference like `&usize` or `Option<foo::bar::Baz>` to a
//! type: The entry point for this is `Ty::from_hir`. //! type: The entry point for this is `TyLoweringContext::lower_ty`.
//! - Building the type for an item: This happens through the `type_for_def` query. //! - Building the type for an item: This happens through the `ty` query.
//! //!
//! This usually involves resolving names, collecting generic arguments etc. //! This usually involves resolving names, collecting generic arguments etc.
use std::{ use std::{
@ -47,7 +47,7 @@ use crate::{
consteval::{intern_const_scalar, path_to_const, unknown_const, unknown_const_as_generic}, consteval::{intern_const_scalar, path_to_const, unknown_const, unknown_const_as_generic},
db::HirDatabase, db::HirDatabase,
make_binders, make_binders,
mapping::ToChalk, mapping::{from_chalk_trait_id, ToChalk},
static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx, static_lifetime, to_assoc_type_id, to_chalk_trait_id, to_placeholder_idx,
utils::Generics, utils::Generics,
utils::{all_super_trait_refs, associated_type_by_name_including_super_traits, generics}, utils::{all_super_trait_refs, associated_type_by_name_including_super_traits, generics},
@ -332,7 +332,10 @@ impl<'a> TyLoweringContext<'a> {
TypeRef::Macro(macro_call) => { TypeRef::Macro(macro_call) => {
let (mut expander, recursion_start) = { let (mut expander, recursion_start) = {
match RefMut::filter_map(self.expander.borrow_mut(), Option::as_mut) { match RefMut::filter_map(self.expander.borrow_mut(), Option::as_mut) {
// There already is an expander here, this means we are already recursing
Ok(expander) => (expander, false), Ok(expander) => (expander, false),
// No expander was created yet, so we are at the start of the expansion recursion
// and therefore have to create an expander.
Err(expander) => ( Err(expander) => (
RefMut::map(expander, |it| { RefMut::map(expander, |it| {
it.insert(Expander::new( it.insert(Expander::new(
@ -362,9 +365,14 @@ impl<'a> TyLoweringContext<'a> {
.exit(self.db.upcast(), mark); .exit(self.db.upcast(), mark);
Some(ty) Some(ty)
} }
_ => None, _ => {
drop(expander);
None
}
} }
}; };
// drop the expander, resetting it to pre-recursion state
if recursion_start { if recursion_start {
*self.expander.borrow_mut() = None; *self.expander.borrow_mut() = None;
} }
@ -974,13 +982,44 @@ impl<'a> TyLoweringContext<'a> {
fn lower_dyn_trait(&self, bounds: &[Interned<TypeBound>]) -> Ty { fn lower_dyn_trait(&self, bounds: &[Interned<TypeBound>]) -> Ty {
let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner); let self_ty = TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, 0)).intern(Interner);
let bounds = self.with_shifted_in(DebruijnIndex::ONE, |ctx| { let bounds = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
QuantifiedWhereClauses::from_iter( let bounds =
bounds.iter().flat_map(|b| ctx.lower_type_bound(b, self_ty.clone(), false));
let mut auto_traits = SmallVec::<[_; 8]>::new();
let mut regular_traits = SmallVec::<[_; 2]>::new();
let mut other_bounds = SmallVec::<[_; 8]>::new();
for bound in bounds {
if let Some(id) = bound.trait_id() {
if ctx.db.trait_data(from_chalk_trait_id(id)).is_auto {
auto_traits.push(bound);
} else {
regular_traits.push(bound);
}
} else {
other_bounds.push(bound);
}
}
if regular_traits.len() > 1 {
return None;
}
auto_traits.sort_unstable_by_key(|b| b.trait_id().unwrap());
auto_traits.dedup();
Some(QuantifiedWhereClauses::from_iter(
Interner, Interner,
bounds.iter().flat_map(|b| ctx.lower_type_bound(b, self_ty.clone(), false)), regular_traits.into_iter().chain(other_bounds).chain(auto_traits),
) ))
}); });
if let Some(bounds) = bounds {
let bounds = crate::make_single_type_binders(bounds); let bounds = crate::make_single_type_binders(bounds);
TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(Interner) TyKind::Dyn(DynTy { bounds, lifetime: static_lifetime() }).intern(Interner)
} else {
// FIXME: report error (additional non-auto traits)
TyKind::Error.intern(Interner)
}
} }
fn lower_impl_trait( fn lower_impl_trait(

View file

@ -193,8 +193,6 @@ fn expr_macro_def_expanded_in_various_places() {
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize
!0..6 '1isize': isize
39..442 '{ ...!(); }': () 39..442 '{ ...!(); }': ()
73..94 'spam!(...am!())': {unknown} 73..94 'spam!(...am!())': {unknown}
100..119 'for _ ...!() {}': () 100..119 'for _ ...!() {}': ()
@ -276,8 +274,6 @@ fn expr_macro_rules_expanded_in_various_places() {
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize !0..6 '1isize': isize
!0..6 '1isize': isize
!0..6 '1isize': isize
53..456 '{ ...!(); }': () 53..456 '{ ...!(); }': ()
87..108 'spam!(...am!())': {unknown} 87..108 'spam!(...am!())': {unknown}
114..133 'for _ ...!() {}': () 114..133 'for _ ...!() {}': ()
@ -312,7 +308,6 @@ fn expr_macro_expanded_in_stmts() {
} }
"#, "#,
expect![[r#" expect![[r#"
!0..8 'leta=();': ()
!3..4 'a': () !3..4 'a': ()
!5..7 '()': () !5..7 '()': ()
57..84 '{ ...); } }': () 57..84 '{ ...); } }': ()
@ -321,7 +316,7 @@ fn expr_macro_expanded_in_stmts() {
} }
#[test] #[test]
fn recurisve_macro_expanded_in_stmts() { fn recursive_macro_expanded_in_stmts() {
check_infer( check_infer(
r#" r#"
macro_rules! ng { macro_rules! ng {
@ -340,11 +335,6 @@ fn recurisve_macro_expanded_in_stmts() {
} }
"#, "#,
expect![[r#" expect![[r#"
!0..7 'leta=3;': ()
!0..13 'ng!{[leta=3]}': ()
!0..13 'ng!{[leta=]3}': ()
!0..13 'ng!{[leta]=3}': ()
!0..13 'ng!{[let]a=3}': ()
!3..4 'a': i32 !3..4 'a': i32
!5..6 '3': i32 !5..6 '3': i32
196..237 '{ ...= a; }': () 196..237 '{ ...= a; }': ()
@ -369,8 +359,6 @@ fn recursive_inner_item_macro_rules() {
"#, "#,
expect![[r#" expect![[r#"
!0..1 '1': i32 !0..1 '1': i32
!0..7 'mac!($)': ()
!0..26 'macro_...>{1};}': ()
107..143 '{ ...!(); }': () 107..143 '{ ...!(); }': ()
129..130 'a': i32 129..130 'a': i32
"#]], "#]],

View file

@ -573,7 +573,6 @@ fn issue_6811() {
} }
"#, "#,
expect![[r#" expect![[r#"
!0..16 'let_a=...t_b=1;': ()
!3..5 '_a': i32 !3..5 '_a': i32
!6..7 '1': i32 !6..7 '1': i32
!11..13 '_b': i32 !11..13 '_b': i32
@ -1679,7 +1678,6 @@ fn main() {
#[test] #[test]
fn trailing_empty_macro() { fn trailing_empty_macro() {
cov_mark::check!(empty_macro_in_trailing_position_is_removed);
check_no_mismatches( check_no_mismatches(
r#" r#"
macro_rules! m2 { macro_rules! m2 {

View file

@ -2549,7 +2549,6 @@ impl B for Astruct {}
expect![[r#" expect![[r#"
569..573 'self': Box<[T], A> 569..573 'self': Box<[T], A>
602..634 '{ ... }': Vec<T, A> 602..634 '{ ... }': Vec<T, A>
612..628 'unimpl...ted!()': Vec<T, A>
648..761 '{ ...t]); }': () 648..761 '{ ...t]); }': ()
658..661 'vec': Vec<i32, Global> 658..661 'vec': Vec<i32, Global>
664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global> 664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global>
@ -3070,3 +3069,17 @@ fn main() {
"#, "#,
); );
} }
#[test]
fn nested_break() {
check_no_mismatches(
r#"
fn func() {
let int = loop {
break 0;
break (break 0);
};
}
"#,
);
}

View file

@ -3833,3 +3833,95 @@ fn test() {
"#, "#,
) )
} }
#[test]
fn dyn_multiple_auto_traits_in_different_order() {
check_no_mismatches(
r#"
auto trait Send {}
auto trait Sync {}
fn f(t: &(dyn Sync + Send)) {}
fn g(t: &(dyn Send + Sync)) {
f(t);
}
"#,
);
check_no_mismatches(
r#"
auto trait Send {}
auto trait Sync {}
trait T {}
fn f(t: &(dyn T + Send + Sync)) {}
fn g(t: &(dyn Sync + T + Send)) {
f(t);
}
"#,
);
check_infer_with_mismatches(
r#"
auto trait Send {}
auto trait Sync {}
trait T1 {}
trait T2 {}
fn f(t: &(dyn T1 + T2 + Send + Sync)) {}
fn g(t: &(dyn Sync + T2 + T1 + Send)) {
f(t);
}
"#,
expect![[r#"
68..69 't': &{unknown}
101..103 '{}': ()
109..110 't': &{unknown}
142..155 '{ f(t); }': ()
148..149 'f': fn f(&{unknown})
148..152 'f(t)': ()
150..151 't': &{unknown}
"#]],
);
check_no_mismatches(
r#"
auto trait Send {}
auto trait Sync {}
trait T {
type Proj: Send + Sync;
}
fn f(t: &(dyn T<Proj = ()> + Send + Sync)) {}
fn g(t: &(dyn Sync + T<Proj = ()> + Send)) {
f(t);
}
"#,
);
}
#[test]
fn dyn_duplicate_auto_trait() {
check_no_mismatches(
r#"
auto trait Send {}
fn f(t: &(dyn Send + Send)) {}
fn g(t: &(dyn Send)) {
f(t);
}
"#,
);
check_no_mismatches(
r#"
auto trait Send {}
trait T {}
fn f(t: &(dyn T + Send + Send)) {}
fn g(t: &(dyn T + Send)) {
f(t);
}
"#,
);
}

View file

@ -124,6 +124,7 @@ pub struct NoSuchField {
#[derive(Debug)] #[derive(Debug)]
pub struct BreakOutsideOfLoop { pub struct BreakOutsideOfLoop {
pub expr: InFile<AstPtr<ast::Expr>>, pub expr: InFile<AstPtr<ast::Expr>>,
pub is_break: bool,
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -1216,11 +1216,11 @@ impl DefWithBody {
let field = source_map.field_syntax(*expr); let field = source_map.field_syntax(*expr);
acc.push(NoSuchField { field }.into()) acc.push(NoSuchField { field }.into())
} }
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => { &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
let expr = source_map let expr = source_map
.expr_syntax(*expr) .expr_syntax(expr)
.expect("break outside of loop in synthetic syntax"); .expect("break outside of loop in synthetic syntax");
acc.push(BreakOutsideOfLoop { expr }.into()) acc.push(BreakOutsideOfLoop { expr, is_break }.into())
} }
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
match source_map.expr_syntax(*call_expr) { match source_map.expr_syntax(*call_expr) {

View file

@ -140,11 +140,19 @@ impl SourceAnalyzer {
) -> Option<InFile<ast::Expr>> { ) -> Option<InFile<ast::Expr>> {
let macro_file = self.body_source_map()?.node_macro_file(expr.as_ref())?; let macro_file = self.body_source_map()?.node_macro_file(expr.as_ref())?;
let expanded = db.parse_or_expand(macro_file)?; let expanded = db.parse_or_expand(macro_file)?;
let res = if let Some(stmts) = ast::MacroStmts::cast(expanded.clone()) {
let res = match ast::MacroCall::cast(expanded.clone()) { match stmts.expr()? {
Some(call) => self.expand_expr(db, InFile::new(macro_file, call))?, ast::Expr::MacroExpr(mac) => {
_ => InFile::new(macro_file, ast::Expr::cast(expanded)?), self.expand_expr(db, InFile::new(macro_file, mac.macro_call()?))?
}
expr => InFile::new(macro_file, expr),
}
} else if let Some(call) = ast::MacroCall::cast(expanded.clone()) {
self.expand_expr(db, InFile::new(macro_file, call))?
} else {
InFile::new(macro_file, ast::Expr::cast(expanded)?)
}; };
Some(res) Some(res)
} }

View file

@ -0,0 +1,294 @@
use syntax::ast::{self, AstNode};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: convert_two_arm_bool_match_to_matches_macro
//
// Convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation.
//
// ```
// fn main() {
// match scrutinee$0 {
// Some(val) if val.cond() => true,
// _ => false,
// }
// }
// ```
// ->
// ```
// fn main() {
// matches!(scrutinee, Some(val) if val.cond())
// }
// ```
pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
let match_arm_list = match_expr.match_arm_list()?;
let mut arms = match_arm_list.arms();
let first_arm = arms.next()?;
let second_arm = arms.next()?;
if arms.next().is_some() {
cov_mark::hit!(non_two_arm_match);
return None;
}
let first_arm_expr = first_arm.expr();
let second_arm_expr = second_arm.expr();
let invert_matches = if is_bool_literal_expr(&first_arm_expr, true)
&& is_bool_literal_expr(&second_arm_expr, false)
{
false
} else if is_bool_literal_expr(&first_arm_expr, false)
&& is_bool_literal_expr(&second_arm_expr, true)
{
true
} else {
cov_mark::hit!(non_invert_bool_literal_arms);
return None;
};
let target_range = ctx.sema.original_range(match_expr.syntax()).range;
let expr = match_expr.expr()?;
acc.add(
AssistId("convert_two_arm_bool_match_to_matches_macro", AssistKind::RefactorRewrite),
"Convert to matches!",
target_range,
|builder| {
let mut arm_str = String::new();
if let Some(ref pat) = first_arm.pat() {
arm_str += &pat.to_string();
}
if let Some(ref guard) = first_arm.guard() {
arm_str += &format!(" {}", &guard.to_string());
}
if invert_matches {
builder.replace(target_range, format!("!matches!({}, {})", expr, arm_str));
} else {
builder.replace(target_range, format!("matches!({}, {})", expr, arm_str));
}
},
)
}
fn is_bool_literal_expr(expr: &Option<ast::Expr>, expect_bool: bool) -> bool {
if let Some(ast::Expr::Literal(lit)) = expr {
if let ast::LiteralKind::Bool(b) = lit.kind() {
return b == expect_bool;
}
}
return false;
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
use super::convert_two_arm_bool_match_to_matches_macro;
#[test]
fn not_applicable_outside_of_range_left() {
check_assist_not_applicable(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
$0 match a {
Some(_val) => true,
_ => false
}
}
"#,
);
}
#[test]
fn not_applicable_non_two_arm_match() {
cov_mark::check!(non_two_arm_match);
check_assist_not_applicable(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(3) => true,
Some(4) => true,
_ => false
}
}
"#,
);
}
#[test]
fn not_applicable_non_bool_literal_arms() {
cov_mark::check!(non_invert_bool_literal_arms);
check_assist_not_applicable(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(val) => val == 3,
_ => false
}
}
"#,
);
}
#[test]
fn not_applicable_both_false_arms() {
cov_mark::check!(non_invert_bool_literal_arms);
check_assist_not_applicable(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(val) => false,
_ => false
}
}
"#,
);
}
#[test]
fn not_applicable_both_true_arms() {
cov_mark::check!(non_invert_bool_literal_arms);
check_assist_not_applicable(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(val) => true,
_ => true
}
}
"#,
);
}
#[test]
fn convert_simple_case() {
check_assist(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(_val) => true,
_ => false
}
}
"#,
r#"
fn foo(a: Option<u32>) -> bool {
matches!(a, Some(_val))
}
"#,
);
}
#[test]
fn convert_simple_invert_case() {
check_assist(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(_val) => false,
_ => true
}
}
"#,
r#"
fn foo(a: Option<u32>) -> bool {
!matches!(a, Some(_val))
}
"#,
);
}
#[test]
fn convert_with_guard_case() {
check_assist(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(val) if val > 3 => true,
_ => false
}
}
"#,
r#"
fn foo(a: Option<u32>) -> bool {
matches!(a, Some(val) if val > 3)
}
"#,
);
}
#[test]
fn convert_enum_match_cases() {
check_assist(
convert_two_arm_bool_match_to_matches_macro,
r#"
enum X { A, B }
fn foo(a: X) -> bool {
match a$0 {
X::A => true,
_ => false
}
}
"#,
r#"
enum X { A, B }
fn foo(a: X) -> bool {
matches!(a, X::A)
}
"#,
);
}
#[test]
fn convert_target_simple() {
check_assist_target(
convert_two_arm_bool_match_to_matches_macro,
r#"
fn foo(a: Option<u32>) -> bool {
match a$0 {
Some(val) => true,
_ => false
}
}
"#,
r#"match a {
Some(val) => true,
_ => false
}"#,
);
}
#[test]
fn convert_target_complex() {
check_assist_target(
convert_two_arm_bool_match_to_matches_macro,
r#"
enum E { X, Y }
fn main() {
match E::X$0 {
E::X => true,
_ => false,
}
}
"#,
"match E::X {
E::X => true,
_ => false,
}",
);
}
}

View file

@ -101,21 +101,22 @@ pub(crate) fn extract_struct_from_enum_variant(
}); });
} }
let indent = enum_ast.indent_level();
let generic_params = enum_ast let generic_params = enum_ast
.generic_param_list() .generic_param_list()
.and_then(|known_generics| extract_generic_params(&known_generics, &field_list)); .and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
let generics = generic_params.as_ref().map(|generics| generics.clone_for_update()); let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
let def = let def =
create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast); create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
let enum_ast = variant.parent_enum();
let indent = enum_ast.indent_level();
def.reindent_to(indent); def.reindent_to(indent);
let start_offset = &variant.parent_enum().syntax().clone(); ted::insert_all(
ted::insert_all_raw( ted::Position::before(enum_ast.syntax()),
ted::Position::before(start_offset),
vec![ vec![
def.syntax().clone().into(), def.syntax().clone().into(),
make::tokens::whitespace(&format!("\n\n{}", indent)).into(), make::tokens::whitespace(&format!("\n\n{indent}")).into(),
], ],
); );
@ -227,7 +228,7 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b
} }
fn create_struct_def( fn create_struct_def(
variant_name: ast::Name, name: ast::Name,
variant: &ast::Variant, variant: &ast::Variant,
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
generics: Option<ast::GenericParamList>, generics: Option<ast::GenericParamList>,
@ -269,43 +270,27 @@ fn create_struct_def(
field_list.into() field_list.into()
} }
}; };
field_list.reindent_to(IndentLevel::single()); field_list.reindent_to(IndentLevel::single());
let strukt = make::struct_(enum_vis, variant_name, generics, field_list).clone_for_update(); let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();
// FIXME: Consider making this an actual function somewhere (like in `AttrsOwnerEdit`) after some deliberation // take comments from variant
let attrs_and_docs = |node: &SyntaxNode| { ted::insert_all(
let mut select_next_ws = false; ted::Position::first_child_of(strukt.syntax()),
node.children_with_tokens().filter(move |child| { take_all_comments(variant.syntax()),
let accept = match child.kind() { );
ATTR | COMMENT => {
select_next_ws = true;
return true;
}
WHITESPACE if select_next_ws => true,
_ => false,
};
select_next_ws = false;
accept
})
};
// copy attributes & comments from variant
let variant_attrs = attrs_and_docs(variant.syntax())
.map(|tok| match tok.kind() {
WHITESPACE => make::tokens::single_newline().into(),
_ => tok,
})
.collect();
ted::insert_all(ted::Position::first_child_of(strukt.syntax()), variant_attrs);
// copy attributes from enum // copy attributes from enum
ted::insert_all( ted::insert_all(
ted::Position::first_child_of(strukt.syntax()), ted::Position::first_child_of(strukt.syntax()),
enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(), enum_
.attrs()
.flat_map(|it| {
vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
})
.collect(),
); );
strukt strukt
} }
@ -346,16 +331,48 @@ fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList
}) })
.unwrap_or_else(|| make::ty(&name.text())); .unwrap_or_else(|| make::ty(&name.text()));
// change from a record to a tuple field list
let tuple_field = make::tuple_field(None, ty); let tuple_field = make::tuple_field(None, ty);
let replacement = make::variant( let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update();
name, ted::replace(variant.field_list()?.syntax(), field_list.syntax());
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
) // remove any ws after the name
.clone_for_update(); if let Some(ws) = name
ted::replace(variant.syntax(), replacement.syntax()); .syntax()
.siblings_with_tokens(syntax::Direction::Next)
.find_map(|tok| tok.into_token().filter(|tok| tok.kind() == WHITESPACE))
{
ted::remove(SyntaxElement::Token(ws));
}
Some(()) Some(())
} }
// Note: this also detaches whitespace after comments,
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
fn take_all_comments(node: &SyntaxNode) -> Vec<SyntaxElement> {
let mut remove_next_ws = false;
node.children_with_tokens()
.filter_map(move |child| match child.kind() {
COMMENT => {
remove_next_ws = true;
child.detach();
Some(child)
}
WHITESPACE if remove_next_ws => {
remove_next_ws = false;
child.detach();
Some(make::tokens::single_newline().into())
}
_ => {
remove_next_ws = false;
None
}
})
.collect()
}
fn apply_references( fn apply_references(
insert_use_cfg: InsertUseConfig, insert_use_cfg: InsertUseConfig,
segment: ast::PathSegment, segment: ast::PathSegment,
@ -480,10 +497,14 @@ enum En<T> { Var(Var<T>) }"#,
fn test_extract_struct_carries_over_attributes() { fn test_extract_struct_carries_over_attributes() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#"#[derive(Debug)] r#"
#[derive(Debug)]
#[derive(Clone)] #[derive(Clone)]
enum Enum { Variant{ field: u32$0 } }"#, enum Enum { Variant{ field: u32$0 } }"#,
r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ field: u32 } r#"
#[derive(Debug)]
#[derive(Clone)]
struct Variant{ field: u32 }
#[derive(Debug)] #[derive(Debug)]
#[derive(Clone)] #[derive(Clone)]
@ -614,7 +635,7 @@ enum A { One(One) }"#,
} }
#[test] #[test]
fn test_extract_struct_keep_comments_and_attrs_on_variant_struct() { fn test_extract_struct_move_struct_variant_comments() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#" r#"
@ -631,19 +652,19 @@ enum A {
/* comment */ /* comment */
// other // other
/// comment /// comment
#[attr]
struct One{ struct One{
a: u32 a: u32
} }
enum A { enum A {
#[attr]
One(One) One(One)
}"#, }"#,
); );
} }
#[test] #[test]
fn test_extract_struct_keep_comments_and_attrs_on_variant_tuple() { fn test_extract_struct_move_tuple_variant_comments() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#" r#"
@ -658,10 +679,10 @@ enum A {
/* comment */ /* comment */
// other // other
/// comment /// comment
#[attr]
struct One(u32, u32); struct One(u32, u32);
enum A { enum A {
#[attr]
One(One) One(One)
}"#, }"#,
); );

View file

@ -0,0 +1,364 @@
use ide_db::{
assists::{AssistId, AssistKind},
famous_defs::FamousDefs,
};
use syntax::{
ast::{self, make, Expr, HasArgList},
AstNode,
};
use crate::{AssistContext, Assists};
// Assist: replace_or_with_or_else
//
// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`.
//
// ```
// # //- minicore:option
// fn foo() {
// let a = Some(1);
// a.unwra$0p_or(2);
// }
// ```
// ->
// ```
// fn foo() {
// let a = Some(1);
// a.unwrap_or_else(|| 2);
// }
// ```
pub(crate) fn replace_or_with_or_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
let kind = is_option_or_result(call.receiver()?, ctx)?;
let (name, arg_list) = (call.name_ref()?, call.arg_list()?);
let mut map_or = false;
let replace = match &*name.text() {
"unwrap_or" => "unwrap_or_else".to_string(),
"or" => "or_else".to_string(),
"ok_or" if kind == Kind::Option => "ok_or_else".to_string(),
"map_or" => {
map_or = true;
"map_or_else".to_string()
}
_ => return None,
};
let arg = match arg_list.args().collect::<Vec<_>>().as_slice() {
[] => make::arg_list(Vec::new()),
[first] => {
let param = into_closure(first);
make::arg_list(vec![param])
}
[first, second] if map_or => {
let param = into_closure(first);
make::arg_list(vec![param, second.clone()])
}
_ => return None,
};
acc.add(
AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite),
format!("Replace {} with {}", name.text(), replace),
call.syntax().text_range(),
|builder| {
builder.replace(name.syntax().text_range(), replace);
builder.replace_ast(arg_list, arg)
},
)
}
fn into_closure(param: &Expr) -> Expr {
(|| {
if let ast::Expr::CallExpr(call) = param {
if call.arg_list()?.args().count() == 0 {
Some(call.expr()?.clone())
} else {
None
}
} else {
None
}
})()
.unwrap_or_else(|| make::expr_closure(None, param.clone()))
}
// Assist: replace_or_else_with_or
//
// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`.
//
// ```
// # //- minicore:option
// fn foo() {
// let a = Some(1);
// a.unwra$0p_or_else(|| 2);
// }
// ```
// ->
// ```
// fn foo() {
// let a = Some(1);
// a.unwrap_or(2);
// }
// ```
pub(crate) fn replace_or_else_with_or(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
let kind = is_option_or_result(call.receiver()?, ctx)?;
let (name, arg_list) = (call.name_ref()?, call.arg_list()?);
let mut map_or = false;
let replace = match &*name.text() {
"unwrap_or_else" => "unwrap_or".to_string(),
"or_else" => "or".to_string(),
"ok_or_else" if kind == Kind::Option => "ok_or".to_string(),
"map_or_else" => {
map_or = true;
"map_or".to_string()
}
_ => return None,
};
let arg = match arg_list.args().collect::<Vec<_>>().as_slice() {
[] => make::arg_list(Vec::new()),
[first] => {
let param = into_call(first);
make::arg_list(vec![param])
}
[first, second] if map_or => {
let param = into_call(first);
make::arg_list(vec![param, second.clone()])
}
_ => return None,
};
acc.add(
AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite),
format!("Replace {} with {}", name.text(), replace),
call.syntax().text_range(),
|builder| {
builder.replace(name.syntax().text_range(), replace);
builder.replace_ast(arg_list, arg)
},
)
}
fn into_call(param: &Expr) -> Expr {
(|| {
if let ast::Expr::ClosureExpr(closure) = param {
if closure.param_list()?.params().count() == 0 {
Some(closure.body()?.clone())
} else {
None
}
} else {
None
}
})()
.unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new())))
}
#[derive(PartialEq, Eq)]
enum Kind {
Option,
Result,
}
fn is_option_or_result(receiver: Expr, ctx: &AssistContext<'_>) -> Option<Kind> {
let ty = ctx.sema.type_of_expr(&receiver)?.adjusted().as_adt()?.as_enum()?;
let option_enum =
FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_option_Option();
if let Some(option_enum) = option_enum {
if ty == option_enum {
return Some(Kind::Option);
}
}
let result_enum =
FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_result_Result();
if let Some(result_enum) = result_enum {
if ty == result_enum {
return Some(Kind::Result);
}
}
None
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn replace_or_with_or_else_simple() {
check_assist(
replace_or_with_or_else,
r#"
//- minicore: option
fn foo() {
let foo = Some(1);
return foo.unwrap_$0or(2);
}
"#,
r#"
fn foo() {
let foo = Some(1);
return foo.unwrap_or_else(|| 2);
}
"#,
)
}
#[test]
fn replace_or_with_or_else_call() {
check_assist(
replace_or_with_or_else,
r#"
//- minicore: option
fn foo() {
let foo = Some(1);
return foo.unwrap_$0or(x());
}
"#,
r#"
fn foo() {
let foo = Some(1);
return foo.unwrap_or_else(x);
}
"#,
)
}
#[test]
fn replace_or_with_or_else_block() {
check_assist(
replace_or_with_or_else,
r#"
//- minicore: option
fn foo() {
let foo = Some(1);
return foo.unwrap_$0or({
let mut x = bar();
for i in 0..10 {
x += i;
}
x
});
}
"#,
r#"
fn foo() {
let foo = Some(1);
return foo.unwrap_or_else(|| {
let mut x = bar();
for i in 0..10 {
x += i;
}
x
});
}
"#,
)
}
#[test]
fn replace_or_else_with_or_simple() {
check_assist(
replace_or_else_with_or,
r#"
//- minicore: option
fn foo() {
let foo = Some(1);
return foo.unwrap_$0or_else(|| 2);
}
"#,
r#"
fn foo() {
let foo = Some(1);
return foo.unwrap_or(2);
}
"#,
)
}
#[test]
fn replace_or_else_with_or_call() {
check_assist(
replace_or_else_with_or,
r#"
//- minicore: option
fn foo() {
let foo = Some(1);
return foo.unwrap_$0or_else(x);
}
"#,
r#"
fn foo() {
let foo = Some(1);
return foo.unwrap_or(x());
}
"#,
)
}
#[test]
fn replace_or_else_with_or_result() {
check_assist(
replace_or_else_with_or,
r#"
//- minicore: result
fn foo() {
let foo = Ok(1);
return foo.unwrap_$0or_else(x);
}
"#,
r#"
fn foo() {
let foo = Ok(1);
return foo.unwrap_or(x());
}
"#,
)
}
#[test]
fn replace_or_else_with_or_map() {
check_assist(
replace_or_else_with_or,
r#"
//- minicore: result
fn foo() {
let foo = Ok("foo");
return foo.map$0_or_else(|| 42, |v| v.len());
}
"#,
r#"
fn foo() {
let foo = Ok("foo");
return foo.map_or(42, |v| v.len());
}
"#,
)
}
#[test]
fn replace_or_else_with_or_not_applicable() {
check_assist_not_applicable(
replace_or_else_with_or,
r#"
fn foo() {
let foo = Ok(1);
return foo.unwrap_$0or_else(x);
}
"#,
)
}
}

View file

@ -1,5 +1,6 @@
use hir::HirDisplay;
use syntax::{ use syntax::{
ast::{Expr, GenericArg}, ast::{Expr, GenericArg, GenericArgList},
ast::{LetStmt, Type::InferType}, ast::{LetStmt, Type::InferType},
AstNode, TextRange, AstNode, TextRange,
}; };
@ -34,21 +35,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
let initializer = let_stmt.initializer()?; let initializer = let_stmt.initializer()?;
let generic_args = match &initializer { let generic_args = generic_arg_list(&initializer)?;
Expr::MethodCallExpr(ce) => ce.generic_arg_list()?,
Expr::CallExpr(ce) => {
if let Expr::PathExpr(pe) = ce.expr()? {
pe.path()?.segment()?.generic_arg_list()?
} else {
cov_mark::hit!(not_applicable_if_non_path_function_call);
return None;
}
}
_ => {
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
return None;
}
};
// Find range of ::<_> // Find range of ::<_>
let colon2 = generic_args.coloncolon_token()?; let colon2 = generic_args.coloncolon_token()?;
@ -65,7 +52,16 @@ pub(crate) fn replace_turbofish_with_explicit_type(
// An improvement would be to check that this is correctly part of the return value of the // An improvement would be to check that this is correctly part of the return value of the
// function call, or sub in the actual return type. // function call, or sub in the actual return type.
let turbofish_type = &turbofish_args[0]; let returned_type = match ctx.sema.type_of_expr(&initializer) {
Some(returned_type) if !returned_type.original.contains_unknown() => {
let module = ctx.sema.scope(let_stmt.syntax())?.module();
returned_type.original.display_source_code(ctx.db(), module.into()).ok()?
}
_ => {
cov_mark::hit!(fallback_to_turbofish_type_if_type_info_not_available);
turbofish_args[0].to_string()
}
};
let initializer_start = initializer.syntax().text_range().start(); let initializer_start = initializer.syntax().text_range().start();
if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start { if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start {
@ -83,7 +79,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
"Replace turbofish with explicit type", "Replace turbofish with explicit type",
TextRange::new(initializer_start, turbofish_range.end()), TextRange::new(initializer_start, turbofish_range.end()),
|builder| { |builder| {
builder.insert(ident_range.end(), format!(": {}", turbofish_type)); builder.insert(ident_range.end(), format!(": {}", returned_type));
builder.delete(turbofish_range); builder.delete(turbofish_range);
}, },
); );
@ -98,7 +94,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
"Replace `_` with turbofish type", "Replace `_` with turbofish type",
turbofish_range, turbofish_range,
|builder| { |builder| {
builder.replace(underscore_range, turbofish_type.to_string()); builder.replace(underscore_range, returned_type);
builder.delete(turbofish_range); builder.delete(turbofish_range);
}, },
); );
@ -107,6 +103,26 @@ pub(crate) fn replace_turbofish_with_explicit_type(
None None
} }
fn generic_arg_list(expr: &Expr) -> Option<GenericArgList> {
match expr {
Expr::MethodCallExpr(expr) => expr.generic_arg_list(),
Expr::CallExpr(expr) => {
if let Expr::PathExpr(pe) = expr.expr()? {
pe.path()?.segment()?.generic_arg_list()
} else {
cov_mark::hit!(not_applicable_if_non_path_function_call);
return None;
}
}
Expr::AwaitExpr(expr) => generic_arg_list(&expr.expr()?),
Expr::TryExpr(expr) => generic_arg_list(&expr.expr()?),
_ => {
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
None
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -115,6 +131,7 @@ mod tests {
#[test] #[test]
fn replaces_turbofish_for_vec_string() { fn replaces_turbofish_for_vec_string() {
cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available);
check_assist( check_assist(
replace_turbofish_with_explicit_type, replace_turbofish_with_explicit_type,
r#" r#"
@ -135,6 +152,7 @@ fn main() {
#[test] #[test]
fn replaces_method_calls() { fn replaces_method_calls() {
// foo.make() is a method call which uses a different expr in the let initializer // foo.make() is a method call which uses a different expr in the let initializer
cov_mark::check!(fallback_to_turbofish_type_if_type_info_not_available);
check_assist( check_assist(
replace_turbofish_with_explicit_type, replace_turbofish_with_explicit_type,
r#" r#"
@ -237,6 +255,110 @@ fn make<T>() -> T {}
fn main() { fn main() {
let a = make$0::<Vec<String>, i32>(); let a = make$0::<Vec<String>, i32>();
} }
"#,
);
}
#[test]
fn replaces_turbofish_for_known_type() {
check_assist(
replace_turbofish_with_explicit_type,
r#"
fn make<T>() -> T {}
fn main() {
let a = make$0::<i32>();
}
"#,
r#"
fn make<T>() -> T {}
fn main() {
let a: i32 = make();
}
"#,
);
check_assist(
replace_turbofish_with_explicit_type,
r#"
//- minicore: option
fn make<T>() -> T {}
fn main() {
let a = make$0::<Option<bool>>();
}
"#,
r#"
fn make<T>() -> T {}
fn main() {
let a: Option<bool> = make();
}
"#,
);
}
#[test]
fn replaces_turbofish_not_same_type() {
check_assist(
replace_turbofish_with_explicit_type,
r#"
//- minicore: option
fn make<T>() -> Option<T> {}
fn main() {
let a = make$0::<u128>();
}
"#,
r#"
fn make<T>() -> Option<T> {}
fn main() {
let a: Option<u128> = make();
}
"#,
);
}
#[test]
fn replaces_turbofish_for_type_with_defaulted_generic_param() {
check_assist(
replace_turbofish_with_explicit_type,
r#"
struct HasDefault<T, U = i32>(T, U);
fn make<T>() -> HasDefault<T> {}
fn main() {
let a = make$0::<bool>();
}
"#,
r#"
struct HasDefault<T, U = i32>(T, U);
fn make<T>() -> HasDefault<T> {}
fn main() {
let a: HasDefault<bool> = make();
}
"#,
);
}
#[test]
fn replaces_turbofish_try_await() {
check_assist(
replace_turbofish_with_explicit_type,
r#"
//- minicore: option, future
struct Fut<T>(T);
impl<T> core::future::Future for Fut<T> {
type Output = Option<T>;
}
fn make<T>() -> Fut<T> {}
fn main() {
let a = make$0::<bool>().await?;
}
"#,
r#"
struct Fut<T>(T);
impl<T> core::future::Future for Fut<T> {
type Output = Option<T>;
}
fn make<T>() -> Fut<T> {}
fn main() {
let a: bool = make().await?;
}
"#, "#,
); );
} }

View file

@ -0,0 +1,293 @@
use syntax::{
algo::neighbor,
ast::{self, edit::IndentLevel, make, AstNode},
ted::{self, Position},
Direction, SyntaxKind, T,
};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: unmerge_match_arm
//
// Splits the current match with a `|` pattern into two arms with identical bodies.
//
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
// match action {
// Action::Move(..) $0| Action::Stop => foo(),
// }
// }
// ```
// ->
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
// match action {
// Action::Move(..) => foo(),
// Action::Stop => foo(),
// }
// }
// ```
pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let pipe_token = ctx.find_token_syntax_at_offset(T![|])?;
let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update();
let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?;
let match_arm_body = match_arm.expr()?;
// We don't need to check for leading pipe because it is directly under `MatchArm`
// without `OrPat`.
let new_parent = match_arm.syntax().parent()?;
let old_parent_range = new_parent.text_range();
acc.add(
AssistId("unmerge_match_arm", AssistKind::RefactorRewrite),
"Unmerge match arm",
pipe_token.text_range(),
|edit| {
let pats_after = pipe_token
.siblings_with_tokens(Direction::Next)
.filter_map(|it| ast::Pat::cast(it.into_node()?));
// FIXME: We should add a leading pipe if the original arm has one.
let new_match_arm = make::match_arm(
pats_after,
match_arm.guard().and_then(|guard| guard.condition()),
match_arm_body,
)
.clone_for_update();
let mut pipe_index = pipe_token.index();
if pipe_token
.prev_sibling_or_token()
.map_or(false, |it| it.kind() == SyntaxKind::WHITESPACE)
{
pipe_index -= 1;
}
or_pat.syntax().splice_children(
pipe_index..or_pat.syntax().children_with_tokens().count(),
Vec::new(),
);
let mut insert_after_old_arm = Vec::new();
// A comma can be:
// - After the arm. In this case we always want to insert a comma after the newly
// inserted arm.
// - Missing after the arm, with no arms after. In this case we want to insert a
// comma before the newly inserted arm. It can not be necessary if there arm
// body is a block, but we don't bother to check that.
// - Missing after the arm with arms after, if the arm body is a block. In this case
// we don't want to insert a comma at all.
let has_comma_after =
std::iter::successors(match_arm.syntax().last_child_or_token(), |it| {
it.prev_sibling_or_token()
})
.map(|it| it.kind())
.skip_while(|it| it.is_trivia())
.next()
== Some(T![,]);
let has_arms_after = neighbor(&match_arm, Direction::Next).is_some();
if !has_comma_after && !has_arms_after {
insert_after_old_arm.push(make::token(T![,]).into());
}
let indent = IndentLevel::from_node(match_arm.syntax());
insert_after_old_arm.push(make::tokens::whitespace(&format!("\n{indent}")).into());
insert_after_old_arm.push(new_match_arm.syntax().clone().into());
ted::insert_all_raw(Position::after(match_arm.syntax()), insert_after_old_arm);
if has_comma_after {
ted::insert_raw(
Position::last_child_of(new_match_arm.syntax()),
make::token(T![,]),
);
}
edit.replace(old_parent_range, new_parent.to_string());
},
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn unmerge_match_arm_single_pipe() {
check_assist(
unmerge_match_arm,
r#"
#[derive(Debug)]
enum X { A, B, C }
fn main() {
let x = X::A;
let y = match x {
X::A $0| X::B => { 1i32 }
X::C => { 2i32 }
};
}
"#,
r#"
#[derive(Debug)]
enum X { A, B, C }
fn main() {
let x = X::A;
let y = match x {
X::A => { 1i32 }
X::B => { 1i32 }
X::C => { 2i32 }
};
}
"#,
);
}
#[test]
fn unmerge_match_arm_guard() {
check_assist(
unmerge_match_arm,
r#"
#[derive(Debug)]
enum X { A, B, C }
fn main() {
let x = X::A;
let y = match x {
X::A $0| X::B if true => { 1i32 }
_ => { 2i32 }
};
}
"#,
r#"
#[derive(Debug)]
enum X { A, B, C }
fn main() {
let x = X::A;
let y = match x {
X::A if true => { 1i32 }
X::B if true => { 1i32 }
_ => { 2i32 }
};
}
"#,
);
}
#[test]
fn unmerge_match_arm_leading_pipe() {
check_assist_not_applicable(
unmerge_match_arm,
r#"
fn main() {
let y = match 0 {
|$0 0 => { 1i32 }
1 => { 2i32 }
};
}
"#,
);
}
#[test]
fn unmerge_match_arm_multiple_pipes() {
check_assist(
unmerge_match_arm,
r#"
#[derive(Debug)]
enum X { A, B, C, D, E }
fn main() {
let x = X::A;
let y = match x {
X::A | X::B |$0 X::C | X::D => 1i32,
X::E => 2i32,
};
}
"#,
r#"
#[derive(Debug)]
enum X { A, B, C, D, E }
fn main() {
let x = X::A;
let y = match x {
X::A | X::B => 1i32,
X::C | X::D => 1i32,
X::E => 2i32,
};
}
"#,
);
}
#[test]
fn unmerge_match_arm_inserts_comma_if_required() {
check_assist(
unmerge_match_arm,
r#"
#[derive(Debug)]
enum X { A, B }
fn main() {
let x = X::A;
let y = match x {
X::A $0| X::B => 1i32
};
}
"#,
r#"
#[derive(Debug)]
enum X { A, B }
fn main() {
let x = X::A;
let y = match x {
X::A => 1i32,
X::B => 1i32
};
}
"#,
);
}
#[test]
fn unmerge_match_arm_inserts_comma_if_had_after() {
check_assist(
unmerge_match_arm,
r#"
#[derive(Debug)]
enum X { A, B }
fn main() {
let x = X::A;
match x {
X::A $0| X::B => {},
}
}
"#,
r#"
#[derive(Debug)]
enum X { A, B }
fn main() {
let x = X::A;
match x {
X::A => {},
X::B => {},
}
}
"#,
);
}
}

View file

@ -122,6 +122,7 @@ mod handlers {
mod convert_let_else_to_match; mod convert_let_else_to_match;
mod convert_tuple_struct_to_named_struct; mod convert_tuple_struct_to_named_struct;
mod convert_to_guarded_return; mod convert_to_guarded_return;
mod convert_two_arm_bool_match_to_matches_macro;
mod convert_while_to_loop; mod convert_while_to_loop;
mod destructure_tuple_binding; mod destructure_tuple_binding;
mod expand_glob_import; mod expand_glob_import;
@ -179,12 +180,14 @@ mod handlers {
mod replace_try_expr_with_match; mod replace_try_expr_with_match;
mod replace_derive_with_manual_impl; mod replace_derive_with_manual_impl;
mod replace_if_let_with_match; mod replace_if_let_with_match;
mod replace_or_with_or_else;
mod introduce_named_generic; mod introduce_named_generic;
mod replace_let_with_if_let; mod replace_let_with_if_let;
mod replace_qualified_name_with_use; mod replace_qualified_name_with_use;
mod replace_string_with_char; mod replace_string_with_char;
mod replace_turbofish_with_explicit_type; mod replace_turbofish_with_explicit_type;
mod split_import; mod split_import;
mod unmerge_match_arm;
mod sort_items; mod sort_items;
mod toggle_ignore; mod toggle_ignore;
mod unmerge_use; mod unmerge_use;
@ -215,6 +218,7 @@ mod handlers {
convert_let_else_to_match::convert_let_else_to_match, convert_let_else_to_match::convert_let_else_to_match,
convert_to_guarded_return::convert_to_guarded_return, convert_to_guarded_return::convert_to_guarded_return,
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
convert_while_to_loop::convert_while_to_loop, convert_while_to_loop::convert_while_to_loop,
destructure_tuple_binding::destructure_tuple_binding, destructure_tuple_binding::destructure_tuple_binding,
expand_glob_import::expand_glob_import, expand_glob_import::expand_glob_import,
@ -273,11 +277,14 @@ mod handlers {
replace_if_let_with_match::replace_if_let_with_match, replace_if_let_with_match::replace_if_let_with_match,
replace_if_let_with_match::replace_match_with_if_let, replace_if_let_with_match::replace_match_with_if_let,
replace_let_with_if_let::replace_let_with_if_let, replace_let_with_if_let::replace_let_with_if_let,
replace_or_with_or_else::replace_or_else_with_or,
replace_or_with_or_else::replace_or_with_or_else,
replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
replace_qualified_name_with_use::replace_qualified_name_with_use, replace_qualified_name_with_use::replace_qualified_name_with_use,
sort_items::sort_items, sort_items::sort_items,
split_import::split_import, split_import::split_import,
toggle_ignore::toggle_ignore, toggle_ignore::toggle_ignore,
unmerge_match_arm::unmerge_match_arm,
unmerge_use::unmerge_use, unmerge_use::unmerge_use,
unnecessary_async::unnecessary_async, unnecessary_async::unnecessary_async,
unwrap_block::unwrap_block, unwrap_block::unwrap_block,

View file

@ -472,6 +472,26 @@ impl Point {
) )
} }
#[test]
fn doctest_convert_two_arm_bool_match_to_matches_macro() {
check_doc_test(
"convert_two_arm_bool_match_to_matches_macro",
r#####"
fn main() {
match scrutinee$0 {
Some(val) if val.cond() => true,
_ => false,
}
}
"#####,
r#####"
fn main() {
matches!(scrutinee, Some(val) if val.cond())
}
"#####,
)
}
#[test] #[test]
fn doctest_convert_while_to_loop() { fn doctest_convert_while_to_loop() {
check_doc_test( check_doc_test(
@ -2009,6 +2029,46 @@ fn handle(action: Action) {
) )
} }
#[test]
fn doctest_replace_or_else_with_or() {
check_doc_test(
"replace_or_else_with_or",
r#####"
//- minicore:option
fn foo() {
let a = Some(1);
a.unwra$0p_or_else(|| 2);
}
"#####,
r#####"
fn foo() {
let a = Some(1);
a.unwrap_or(2);
}
"#####,
)
}
#[test]
fn doctest_replace_or_with_or_else() {
check_doc_test(
"replace_or_with_or_else",
r#####"
//- minicore:option
fn foo() {
let a = Some(1);
a.unwra$0p_or(2);
}
"#####,
r#####"
fn foo() {
let a = Some(1);
a.unwrap_or_else(|| 2);
}
"#####,
)
}
#[test] #[test]
fn doctest_replace_qualified_name_with_use() { fn doctest_replace_qualified_name_with_use() {
check_doc_test( check_doc_test(
@ -2207,6 +2267,32 @@ fn arithmetics {
) )
} }
#[test]
fn doctest_unmerge_match_arm() {
check_doc_test(
"unmerge_match_arm",
r#####"
enum Action { Move { distance: u32 }, Stop }
fn handle(action: Action) {
match action {
Action::Move(..) $0| Action::Stop => foo(),
}
}
"#####,
r#####"
enum Action { Move { distance: u32 }, Stop }
fn handle(action: Action) {
match action {
Action::Move(..) => foo(),
Action::Stop => foo(),
}
}
"#####,
)
}
#[test] #[test]
fn doctest_unmerge_use() { fn doctest_unmerge_use() {
check_doc_test( check_doc_test(

View file

@ -282,14 +282,26 @@ pub(crate) fn complete_expr_path(
} }
} }
if let Some(ty) = innermost_ret_ty { if let Some(ret_ty) = innermost_ret_ty {
add_keyword( add_keyword(
"return", "return",
match (in_block_expr, ty.is_unit()) { match (ret_ty.is_unit(), in_block_expr) {
(true, true) => "return ;", (true, true) => {
(true, false) => "return;", cov_mark::hit!(return_unit_block);
(false, true) => "return $0", "return;"
(false, false) => "return", }
(true, false) => {
cov_mark::hit!(return_unit_no_block);
"return"
}
(false, true) => {
cov_mark::hit!(return_value_block);
"return $0;"
}
(false, false) => {
cov_mark::hit!(return_value_no_block);
"return $0"
}
}, },
); );
} }

View file

@ -1,7 +1,7 @@
//! Completion tests for expressions. //! Completion tests for expressions.
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use crate::tests::{completion_list, BASE_ITEMS_FIXTURE}; use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
fn check(ra_fixture: &str, expect: Expect) { fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture)); let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
@ -670,3 +670,39 @@ fn main() {
"#]], "#]],
); );
} }
#[test]
fn return_unit_block() {
cov_mark::check!(return_unit_block);
check_edit("return", r#"fn f() { if true { $0 } }"#, r#"fn f() { if true { return; } }"#);
}
#[test]
fn return_unit_no_block() {
cov_mark::check!(return_unit_no_block);
check_edit(
"return",
r#"fn f() { match () { () => $0 } }"#,
r#"fn f() { match () { () => return } }"#,
);
}
#[test]
fn return_value_block() {
cov_mark::check!(return_value_block);
check_edit(
"return",
r#"fn f() -> i32 { if true { $0 } }"#,
r#"fn f() -> i32 { if true { return $0; } }"#,
);
}
#[test]
fn return_value_no_block() {
cov_mark::check!(return_value_no_block);
check_edit(
"return",
r#"fn f() -> i32 { match () { () => $0 } }"#,
r#"fn f() -> i32 { match () { () => return $0 } }"#,
);
}

View file

@ -315,7 +315,6 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
| ast::Expr::IndexExpr(_) | ast::Expr::IndexExpr(_)
| ast::Expr::Literal(_) | ast::Expr::Literal(_)
| ast::Expr::MacroExpr(_) | ast::Expr::MacroExpr(_)
| ast::Expr::MacroStmts(_)
| ast::Expr::MethodCallExpr(_) | ast::Expr::MethodCallExpr(_)
| ast::Expr::ParenExpr(_) | ast::Expr::ParenExpr(_)
| ast::Expr::PathExpr(_) | ast::Expr::PathExpr(_)

View file

@ -7,9 +7,10 @@ pub(crate) fn break_outside_of_loop(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::BreakOutsideOfLoop, d: &hir::BreakOutsideOfLoop,
) -> Diagnostic { ) -> Diagnostic {
let construct = if d.is_break { "break" } else { "continue" };
Diagnostic::new( Diagnostic::new(
"break-outside-of-loop", "break-outside-of-loop",
"break outside of loop", format!("{construct} outside of loop"),
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
) )
} }
@ -19,11 +20,122 @@ mod tests {
use crate::tests::check_diagnostics; use crate::tests::check_diagnostics;
#[test] #[test]
fn break_outside_of_loop() { fn outside_of_loop() {
check_diagnostics( check_diagnostics(
r#" r#"
fn foo() { break; } fn foo() {
break;
//^^^^^ error: break outside of loop //^^^^^ error: break outside of loop
break 'a;
//^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
}
"#,
);
}
#[test]
fn try_blocks_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
break;
//^^^^^ error: break outside of loop
break 'a;
//^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
};
}
}
"#,
);
}
#[test]
fn async_blocks_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
break;
//^^^^^ error: break outside of loop
break 'a;
//^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
};
}
}
"#,
);
}
#[test]
fn closures_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
break;
//^^^^^ error: break outside of loop
break 'a;
//^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
};
}
}
"#,
);
}
#[test]
fn blocks_pass_through() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
{
break;
break 'a;
continue;
continue 'a;
}
}
}
"#,
);
}
#[test]
fn label_blocks() {
check_diagnostics(
r#"
fn foo() {
'a: {
break;
//^^^^^ error: break outside of loop
break 'a;
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
}
}
"#, "#,
); );
} }

View file

@ -947,6 +947,50 @@ fn f() {
); );
} }
mod rust_unstable {
use super::*;
#[test]
fn rfc_1872_exhaustive_patterns() {
check_diagnostics_no_bails(
r"
//- minicore: option, result
#![feature(exhaustive_patterns)]
enum Void {}
fn test() {
match None::<!> { None => () }
match Result::<u8, !>::Ok(2) { Ok(_) => () }
match Result::<u8, Void>::Ok(2) { Ok(_) => () }
match (2, loop {}) {}
match Result::<!, !>::Ok(loop {}) {}
match (&loop {}) {} // https://github.com/rust-lang/rust/issues/50642#issuecomment-388234919
// ^^^^^^^^^^ error: missing match arm: type `&!` is non-empty
}",
);
}
#[test]
fn rfc_1872_private_uninhabitedness() {
check_diagnostics_no_bails(
r"
//- minicore: option
//- /lib.rs crate:lib
#![feature(exhaustive_patterns)]
pub struct PrivatelyUninhabited { private_field: Void }
enum Void {}
fn test_local(x: Option<PrivatelyUninhabited>) {
match x {}
} // ^ error: missing match arm: `None` not covered
//- /main.rs crate:main deps:lib
#![feature(exhaustive_patterns)]
fn test(x: Option<lib::PrivatelyUninhabited>) {
match x {}
// ^ error: missing match arm: `None` and `Some(_)` not covered
}",
);
}
}
mod false_negatives { mod false_negatives {
//! The implementation of match checking here is a work in progress. As we roll this out, we //! The implementation of match checking here is a work in progress. As we roll this out, we
//! prefer false negatives to false positives (ideally there would be no false positives). This //! prefer false negatives to false positives (ideally there would be no false positives). This

View file

@ -1910,7 +1910,7 @@ impl<T> Vec<T> {
pub struct Box<T> {} pub struct Box<T> {}
trait Display {} trait Display {}
trait Sync {} auto trait Sync {}
fn main() { fn main() {
// The block expression wrapping disables the constructor hint hiding logic // The block expression wrapping disables the constructor hint hiding logic

View file

@ -118,6 +118,11 @@ fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) {
match mode { match mode {
Mode::Use => {} Mode::Use => {}
Mode::Type => { Mode::Type => {
// test typepathfn_with_coloncolon
// type F = Start::(Middle) -> (Middle)::End;
if p.at(T![::]) && p.nth_at(2, T!['(']) {
p.bump(T![::]);
}
// test path_fn_trait_args // test path_fn_trait_args
// type F = Box<Fn(i32) -> ()>; // type F = Box<Fn(i32) -> ()>;
if p.at(T!['(']) { if p.at(T!['(']) {

View file

@ -262,33 +262,117 @@ pub enum SyntaxKind {
use self::SyntaxKind::*; use self::SyntaxKind::*;
impl SyntaxKind { impl SyntaxKind {
pub fn is_keyword(self) -> bool { pub fn is_keyword(self) -> bool {
match self { matches!(
AS_KW | ASYNC_KW | AWAIT_KW | BOX_KW | BREAK_KW | CONST_KW | CONTINUE_KW | CRATE_KW self,
| DYN_KW | ELSE_KW | ENUM_KW | EXTERN_KW | FALSE_KW | FN_KW | FOR_KW | IF_KW AS_KW
| IMPL_KW | IN_KW | LET_KW | LOOP_KW | MACRO_KW | MATCH_KW | MOD_KW | MOVE_KW | ASYNC_KW
| MUT_KW | PUB_KW | REF_KW | RETURN_KW | SELF_KW | SELF_TYPE_KW | STATIC_KW | AWAIT_KW
| STRUCT_KW | SUPER_KW | TRAIT_KW | TRUE_KW | TRY_KW | TYPE_KW | UNSAFE_KW | USE_KW | BOX_KW
| WHERE_KW | WHILE_KW | YIELD_KW | AUTO_KW | DEFAULT_KW | EXISTENTIAL_KW | UNION_KW | BREAK_KW
| RAW_KW | MACRO_RULES_KW => true, | CONST_KW
_ => false, | CONTINUE_KW
} | CRATE_KW
| DYN_KW
| ELSE_KW
| ENUM_KW
| EXTERN_KW
| FALSE_KW
| FN_KW
| FOR_KW
| IF_KW
| IMPL_KW
| IN_KW
| LET_KW
| LOOP_KW
| MACRO_KW
| MATCH_KW
| MOD_KW
| MOVE_KW
| MUT_KW
| PUB_KW
| REF_KW
| RETURN_KW
| SELF_KW
| SELF_TYPE_KW
| STATIC_KW
| STRUCT_KW
| SUPER_KW
| TRAIT_KW
| TRUE_KW
| TRY_KW
| TYPE_KW
| UNSAFE_KW
| USE_KW
| WHERE_KW
| WHILE_KW
| YIELD_KW
| AUTO_KW
| DEFAULT_KW
| EXISTENTIAL_KW
| UNION_KW
| RAW_KW
| MACRO_RULES_KW
)
} }
pub fn is_punct(self) -> bool { pub fn is_punct(self) -> bool {
match self { matches!(
SEMICOLON | COMMA | L_PAREN | R_PAREN | L_CURLY | R_CURLY | L_BRACK | R_BRACK self,
| L_ANGLE | R_ANGLE | AT | POUND | TILDE | QUESTION | DOLLAR | AMP | PIPE | PLUS SEMICOLON
| STAR | SLASH | CARET | PERCENT | UNDERSCORE | DOT | DOT2 | DOT3 | DOT2EQ | COLON | COMMA
| COLON2 | EQ | EQ2 | FAT_ARROW | BANG | NEQ | MINUS | THIN_ARROW | LTEQ | GTEQ | L_PAREN
| PLUSEQ | MINUSEQ | PIPEEQ | AMPEQ | CARETEQ | SLASHEQ | STAREQ | PERCENTEQ | AMP2 | R_PAREN
| PIPE2 | SHL | SHR | SHLEQ | SHREQ => true, | L_CURLY
_ => false, | R_CURLY
} | L_BRACK
| R_BRACK
| L_ANGLE
| R_ANGLE
| AT
| POUND
| TILDE
| QUESTION
| DOLLAR
| AMP
| PIPE
| PLUS
| STAR
| SLASH
| CARET
| PERCENT
| UNDERSCORE
| DOT
| DOT2
| DOT3
| DOT2EQ
| COLON
| COLON2
| EQ
| EQ2
| FAT_ARROW
| BANG
| NEQ
| MINUS
| THIN_ARROW
| LTEQ
| GTEQ
| PLUSEQ
| MINUSEQ
| PIPEEQ
| AMPEQ
| CARETEQ
| SLASHEQ
| STAREQ
| PERCENTEQ
| AMP2
| PIPE2
| SHL
| SHR
| SHLEQ
| SHREQ
)
} }
pub fn is_literal(self) -> bool { pub fn is_literal(self) -> bool {
match self { matches!(self, INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | BYTE_STRING)
INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE | STRING | BYTE_STRING => true,
_ => false,
}
} }
pub fn from_keyword(ident: &str) -> Option<SyntaxKind> { pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {
let kw = match ident { let kw = match ident {

View file

@ -0,0 +1,43 @@
SOURCE_FILE
TYPE_ALIAS
TYPE_KW "type"
WHITESPACE " "
NAME
IDENT "F"
WHITESPACE " "
EQ "="
WHITESPACE " "
PATH_TYPE
PATH
PATH
PATH_SEGMENT
NAME_REF
IDENT "Start"
COLON2 "::"
PARAM_LIST
L_PAREN "("
PARAM
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Middle"
R_PAREN ")"
WHITESPACE " "
RET_TYPE
THIN_ARROW "->"
WHITESPACE " "
PAREN_TYPE
L_PAREN "("
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Middle"
R_PAREN ")"
COLON2 "::"
PATH_SEGMENT
NAME_REF
IDENT "End"
SEMICOLON ";"
WHITESPACE "\n"

View file

@ -0,0 +1 @@
type F = Start::(Middle) -> (Middle)::End;

View file

@ -116,6 +116,7 @@ pub(crate) struct GlobalStateSnapshot {
pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>, vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
pub(crate) proc_macros_loaded: bool,
} }
impl std::panic::UnwindSafe for GlobalStateSnapshot {} impl std::panic::UnwindSafe for GlobalStateSnapshot {}
@ -256,6 +257,7 @@ impl GlobalState {
check_fixes: Arc::clone(&self.diagnostics.check_fixes), check_fixes: Arc::clone(&self.diagnostics.check_fixes),
mem_docs: self.mem_docs.clone(), mem_docs: self.mem_docs.clone(),
semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(),
} }
} }

View file

@ -1504,7 +1504,11 @@ pub(crate) fn handle_semantic_tokens_full(
let text = snap.analysis.file_text(file_id)?; let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?; let line_index = snap.file_line_index(file_id)?;
let highlights = snap.analysis.highlight(snap.config.highlighting_config(), file_id)?; let mut highlight_config = snap.config.highlighting_config();
// Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded;
let highlights = snap.analysis.highlight(highlight_config, file_id)?;
let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
// Unconditionally cache the tokens // Unconditionally cache the tokens
@ -1523,7 +1527,11 @@ pub(crate) fn handle_semantic_tokens_full_delta(
let text = snap.analysis.file_text(file_id)?; let text = snap.analysis.file_text(file_id)?;
let line_index = snap.file_line_index(file_id)?; let line_index = snap.file_line_index(file_id)?;
let highlights = snap.analysis.highlight(snap.config.highlighting_config(), file_id)?; let mut highlight_config = snap.config.highlighting_config();
// Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet.
highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded;
let highlights = snap.analysis.highlight(highlight_config, file_id)?;
let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
let mut cache = snap.semantic_tokens_cache.lock(); let mut cache = snap.semantic_tokens_cache.lock();

View file

@ -347,8 +347,8 @@ impl GlobalState {
error error
}) })
}) })
.collect(); .collect()
} };
} }
let watch = match files_config.watcher { let watch = match files_config.watcher {

View file

@ -343,7 +343,6 @@ Expr =
| Literal | Literal
| LoopExpr | LoopExpr
| MacroExpr | MacroExpr
| MacroStmts
| MatchExpr | MatchExpr
| MethodCallExpr | MethodCallExpr
| ParenExpr | ParenExpr

View file

@ -1526,7 +1526,6 @@ pub enum Expr {
Literal(Literal), Literal(Literal),
LoopExpr(LoopExpr), LoopExpr(LoopExpr),
MacroExpr(MacroExpr), MacroExpr(MacroExpr),
MacroStmts(MacroStmts),
MatchExpr(MatchExpr), MatchExpr(MatchExpr),
MethodCallExpr(MethodCallExpr), MethodCallExpr(MethodCallExpr),
ParenExpr(ParenExpr), ParenExpr(ParenExpr),
@ -3169,10 +3168,7 @@ impl From<ConstArg> for GenericArg {
} }
impl AstNode for GenericArg { impl AstNode for GenericArg {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(kind, TYPE_ARG | ASSOC_TYPE_ARG | LIFETIME_ARG | CONST_ARG)
TYPE_ARG | ASSOC_TYPE_ARG | LIFETIME_ARG | CONST_ARG => true,
_ => false,
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3237,12 +3233,23 @@ impl From<TupleType> for Type {
} }
impl AstNode for Type { impl AstNode for Type {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
ARRAY_TYPE | DYN_TRAIT_TYPE | FN_PTR_TYPE | FOR_TYPE | IMPL_TRAIT_TYPE | INFER_TYPE kind,
| MACRO_TYPE | NEVER_TYPE | PAREN_TYPE | PATH_TYPE | PTR_TYPE | REF_TYPE ARRAY_TYPE
| SLICE_TYPE | TUPLE_TYPE => true, | DYN_TRAIT_TYPE
_ => false, | FN_PTR_TYPE
} | FOR_TYPE
| IMPL_TRAIT_TYPE
| INFER_TYPE
| MACRO_TYPE
| NEVER_TYPE
| PAREN_TYPE
| PATH_TYPE
| PTR_TYPE
| REF_TYPE
| SLICE_TYPE
| TUPLE_TYPE
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3334,9 +3341,6 @@ impl From<LoopExpr> for Expr {
impl From<MacroExpr> for Expr { impl From<MacroExpr> for Expr {
fn from(node: MacroExpr) -> Expr { Expr::MacroExpr(node) } fn from(node: MacroExpr) -> Expr { Expr::MacroExpr(node) }
} }
impl From<MacroStmts> for Expr {
fn from(node: MacroStmts) -> Expr { Expr::MacroStmts(node) }
}
impl From<MatchExpr> for Expr { impl From<MatchExpr> for Expr {
fn from(node: MatchExpr) -> Expr { Expr::MatchExpr(node) } fn from(node: MatchExpr) -> Expr { Expr::MatchExpr(node) }
} }
@ -3384,15 +3388,41 @@ impl From<UnderscoreExpr> for Expr {
} }
impl AstNode for Expr { impl AstNode for Expr {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BLOCK_EXPR | BOX_EXPR | BREAK_EXPR | CALL_EXPR kind,
| CAST_EXPR | CLOSURE_EXPR | CONTINUE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR ARRAY_EXPR
| INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_EXPR | MACRO_STMTS | MATCH_EXPR | AWAIT_EXPR
| METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR | BIN_EXPR
| RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR | BLOCK_EXPR
| YIELD_EXPR | LET_EXPR | UNDERSCORE_EXPR => true, | BOX_EXPR
_ => false, | BREAK_EXPR
} | CALL_EXPR
| CAST_EXPR
| CLOSURE_EXPR
| CONTINUE_EXPR
| FIELD_EXPR
| FOR_EXPR
| IF_EXPR
| INDEX_EXPR
| LITERAL
| LOOP_EXPR
| MACRO_EXPR
| MATCH_EXPR
| METHOD_CALL_EXPR
| PAREN_EXPR
| PATH_EXPR
| PREFIX_EXPR
| RANGE_EXPR
| RECORD_EXPR
| REF_EXPR
| RETURN_EXPR
| TRY_EXPR
| TUPLE_EXPR
| WHILE_EXPR
| YIELD_EXPR
| LET_EXPR
| UNDERSCORE_EXPR
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3413,7 +3443,6 @@ impl AstNode for Expr {
LITERAL => Expr::Literal(Literal { syntax }), LITERAL => Expr::Literal(Literal { syntax }),
LOOP_EXPR => Expr::LoopExpr(LoopExpr { syntax }), LOOP_EXPR => Expr::LoopExpr(LoopExpr { syntax }),
MACRO_EXPR => Expr::MacroExpr(MacroExpr { syntax }), MACRO_EXPR => Expr::MacroExpr(MacroExpr { syntax }),
MACRO_STMTS => Expr::MacroStmts(MacroStmts { syntax }),
MATCH_EXPR => Expr::MatchExpr(MatchExpr { syntax }), MATCH_EXPR => Expr::MatchExpr(MatchExpr { syntax }),
METHOD_CALL_EXPR => Expr::MethodCallExpr(MethodCallExpr { syntax }), METHOD_CALL_EXPR => Expr::MethodCallExpr(MethodCallExpr { syntax }),
PAREN_EXPR => Expr::ParenExpr(ParenExpr { syntax }), PAREN_EXPR => Expr::ParenExpr(ParenExpr { syntax }),
@ -3452,7 +3481,6 @@ impl AstNode for Expr {
Expr::Literal(it) => &it.syntax, Expr::Literal(it) => &it.syntax,
Expr::LoopExpr(it) => &it.syntax, Expr::LoopExpr(it) => &it.syntax,
Expr::MacroExpr(it) => &it.syntax, Expr::MacroExpr(it) => &it.syntax,
Expr::MacroStmts(it) => &it.syntax,
Expr::MatchExpr(it) => &it.syntax, Expr::MatchExpr(it) => &it.syntax,
Expr::MethodCallExpr(it) => &it.syntax, Expr::MethodCallExpr(it) => &it.syntax,
Expr::ParenExpr(it) => &it.syntax, Expr::ParenExpr(it) => &it.syntax,
@ -3521,11 +3549,25 @@ impl From<Use> for Item {
} }
impl AstNode for Item { impl AstNode for Item {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
CONST | ENUM | EXTERN_BLOCK | EXTERN_CRATE | FN | IMPL | MACRO_CALL | MACRO_RULES kind,
| MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT | TYPE_ALIAS | UNION | USE => true, CONST
_ => false, | ENUM
} | EXTERN_BLOCK
| EXTERN_CRATE
| FN
| IMPL
| MACRO_CALL
| MACRO_RULES
| MACRO_DEF
| MODULE
| STATIC
| STRUCT
| TRAIT
| TYPE_ALIAS
| UNION
| USE
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3629,12 +3671,25 @@ impl From<ConstBlockPat> for Pat {
} }
impl AstNode for Pat { impl AstNode for Pat {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
IDENT_PAT | BOX_PAT | REST_PAT | LITERAL_PAT | MACRO_PAT | OR_PAT | PAREN_PAT kind,
| PATH_PAT | WILDCARD_PAT | RANGE_PAT | RECORD_PAT | REF_PAT | SLICE_PAT IDENT_PAT
| TUPLE_PAT | TUPLE_STRUCT_PAT | CONST_BLOCK_PAT => true, | BOX_PAT
_ => false, | REST_PAT
} | LITERAL_PAT
| MACRO_PAT
| OR_PAT
| PAREN_PAT
| PATH_PAT
| WILDCARD_PAT
| RANGE_PAT
| RECORD_PAT
| REF_PAT
| SLICE_PAT
| TUPLE_PAT
| TUPLE_STRUCT_PAT
| CONST_BLOCK_PAT
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3686,12 +3741,7 @@ impl From<TupleFieldList> for FieldList {
fn from(node: TupleFieldList) -> FieldList { FieldList::TupleFieldList(node) } fn from(node: TupleFieldList) -> FieldList { FieldList::TupleFieldList(node) }
} }
impl AstNode for FieldList { impl AstNode for FieldList {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, RECORD_FIELD_LIST | TUPLE_FIELD_LIST) }
match kind {
RECORD_FIELD_LIST | TUPLE_FIELD_LIST => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
RECORD_FIELD_LIST => FieldList::RecordFieldList(RecordFieldList { syntax }), RECORD_FIELD_LIST => FieldList::RecordFieldList(RecordFieldList { syntax }),
@ -3717,12 +3767,7 @@ impl From<Union> for Adt {
fn from(node: Union) -> Adt { Adt::Union(node) } fn from(node: Union) -> Adt { Adt::Union(node) }
} }
impl AstNode for Adt { impl AstNode for Adt {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, ENUM | STRUCT | UNION) }
match kind {
ENUM | STRUCT | UNION => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
ENUM => Adt::Enum(Enum { syntax }), ENUM => Adt::Enum(Enum { syntax }),
@ -3753,12 +3798,7 @@ impl From<TypeAlias> for AssocItem {
fn from(node: TypeAlias) -> AssocItem { AssocItem::TypeAlias(node) } fn from(node: TypeAlias) -> AssocItem { AssocItem::TypeAlias(node) }
} }
impl AstNode for AssocItem { impl AstNode for AssocItem {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, CONST | FN | MACRO_CALL | TYPE_ALIAS) }
match kind {
CONST | FN | MACRO_CALL | TYPE_ALIAS => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
CONST => AssocItem::Const(Const { syntax }), CONST => AssocItem::Const(Const { syntax }),
@ -3791,12 +3831,7 @@ impl From<TypeAlias> for ExternItem {
fn from(node: TypeAlias) -> ExternItem { ExternItem::TypeAlias(node) } fn from(node: TypeAlias) -> ExternItem { ExternItem::TypeAlias(node) }
} }
impl AstNode for ExternItem { impl AstNode for ExternItem {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, FN | MACRO_CALL | STATIC | TYPE_ALIAS) }
match kind {
FN | MACRO_CALL | STATIC | TYPE_ALIAS => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
FN => ExternItem::Fn(Fn { syntax }), FN => ExternItem::Fn(Fn { syntax }),
@ -3827,10 +3862,7 @@ impl From<TypeParam> for GenericParam {
} }
impl AstNode for GenericParam { impl AstNode for GenericParam {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(kind, CONST_PARAM | LIFETIME_PARAM | TYPE_PARAM)
CONST_PARAM | LIFETIME_PARAM | TYPE_PARAM => true,
_ => false,
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -3856,12 +3888,7 @@ impl AnyHasArgList {
} }
} }
impl AstNode for AnyHasArgList { impl AstNode for AnyHasArgList {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, CALL_EXPR | METHOD_CALL_EXPR) }
match kind {
CALL_EXPR | METHOD_CALL_EXPR => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasArgList { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasArgList { syntax })
} }
@ -3875,7 +3902,8 @@ impl AnyHasAttrs {
} }
impl AstNode for AnyHasAttrs { impl AstNode for AnyHasAttrs {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
kind,
MACRO_CALL MACRO_CALL
| SOURCE_FILE | SOURCE_FILE
| CONST | CONST
@ -3942,9 +3970,8 @@ impl AstNode for AnyHasAttrs {
| MATCH_ARM | MATCH_ARM
| IDENT_PAT | IDENT_PAT
| REST_PAT | REST_PAT
| RECORD_PAT_FIELD => true, | RECORD_PAT_FIELD
_ => false, )
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasAttrs { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasAttrs { syntax })
@ -3959,12 +3986,29 @@ impl AnyHasDocComments {
} }
impl AstNode for AnyHasDocComments { impl AstNode for AnyHasDocComments {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
MACRO_CALL | SOURCE_FILE | CONST | ENUM | EXTERN_BLOCK | EXTERN_CRATE | FN | IMPL kind,
| MACRO_RULES | MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT | TYPE_ALIAS | UNION MACRO_CALL
| USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => true, | SOURCE_FILE
_ => false, | CONST
} | ENUM
| EXTERN_BLOCK
| EXTERN_CRATE
| FN
| IMPL
| MACRO_RULES
| MACRO_DEF
| MODULE
| STATIC
| STRUCT
| TRAIT
| TYPE_ALIAS
| UNION
| USE
| RECORD_FIELD
| TUPLE_FIELD
| VARIANT
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasDocComments { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasDocComments { syntax })
@ -3979,10 +4023,7 @@ impl AnyHasGenericParams {
} }
impl AstNode for AnyHasGenericParams { impl AstNode for AnyHasGenericParams {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(kind, ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION)
ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION => true,
_ => false,
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasGenericParams { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasGenericParams { syntax })
@ -3996,12 +4037,7 @@ impl AnyHasLoopBody {
} }
} }
impl AstNode for AnyHasLoopBody { impl AstNode for AnyHasLoopBody {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, FOR_EXPR | LOOP_EXPR | WHILE_EXPR) }
match kind {
FOR_EXPR | LOOP_EXPR | WHILE_EXPR => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasLoopBody { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasLoopBody { syntax })
} }
@ -4014,12 +4050,7 @@ impl AnyHasModuleItem {
} }
} }
impl AstNode for AnyHasModuleItem { impl AstNode for AnyHasModuleItem {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, MACRO_ITEMS | SOURCE_FILE | ITEM_LIST) }
match kind {
MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => true,
_ => false,
}
}
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasModuleItem { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasModuleItem { syntax })
} }
@ -4033,12 +4064,27 @@ impl AnyHasName {
} }
impl AstNode for AnyHasName { impl AstNode for AnyHasName {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
CONST | ENUM | FN | MACRO_RULES | MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT kind,
| TYPE_ALIAS | UNION | RENAME | SELF_PARAM | RECORD_FIELD | VARIANT | CONST_PARAM CONST
| TYPE_PARAM | IDENT_PAT => true, | ENUM
_ => false, | FN
} | MACRO_RULES
| MACRO_DEF
| MODULE
| STATIC
| STRUCT
| TRAIT
| TYPE_ALIAS
| UNION
| RENAME
| SELF_PARAM
| RECORD_FIELD
| VARIANT
| CONST_PARAM
| TYPE_PARAM
| IDENT_PAT
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasName { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasName { syntax })
@ -4053,10 +4099,10 @@ impl AnyHasTypeBounds {
} }
impl AstNode for AnyHasTypeBounds { impl AstNode for AnyHasTypeBounds {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
ASSOC_TYPE_ARG | TRAIT | TYPE_ALIAS | LIFETIME_PARAM | TYPE_PARAM | WHERE_PRED => true, kind,
_ => false, ASSOC_TYPE_ARG | TRAIT | TYPE_ALIAS | LIFETIME_PARAM | TYPE_PARAM | WHERE_PRED
} )
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasTypeBounds { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasTypeBounds { syntax })
@ -4071,13 +4117,26 @@ impl AnyHasVisibility {
} }
impl AstNode for AnyHasVisibility { impl AstNode for AnyHasVisibility {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(
CONST | ENUM | EXTERN_CRATE | FN | IMPL | MACRO_RULES | MACRO_DEF | MODULE | STATIC kind,
| STRUCT | TRAIT | TYPE_ALIAS | UNION | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => { CONST
true | ENUM
} | EXTERN_CRATE
_ => false, | FN
} | IMPL
| MACRO_RULES
| MACRO_DEF
| MODULE
| STATIC
| STRUCT
| TRAIT
| TYPE_ALIAS
| UNION
| USE
| RECORD_FIELD
| TUPLE_FIELD
| VARIANT
)
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| AnyHasVisibility { syntax }) Self::can_cast(syntax.kind()).then(|| AnyHasVisibility { syntax })

View file

@ -25,7 +25,7 @@ pub mod ext {
return from_text(&name.text()); return from_text(&name.text());
fn from_text(text: &str) -> ast::IdentPat { fn from_text(text: &str) -> ast::IdentPat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
pub fn ident_path(ident: &str) -> ast::Path { pub fn ident_path(ident: &str) -> ast::Path {
@ -60,10 +60,10 @@ pub mod ext {
expr_from_text("todo!()") expr_from_text("todo!()")
} }
pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr { pub fn expr_ty_default(ty: &ast::Type) -> ast::Expr {
expr_from_text(&format!("{}::default()", ty)) expr_from_text(&format!("{ty}::default()"))
} }
pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr { pub fn expr_ty_new(ty: &ast::Type) -> ast::Expr {
expr_from_text(&format!("{}::new()", ty)) expr_from_text(&format!("{ty}::new()"))
} }
pub fn zero_number() -> ast::Expr { pub fn zero_number() -> ast::Expr {
@ -92,18 +92,20 @@ pub mod ext {
ty_path(ident_path("bool")) ty_path(ident_path("bool"))
} }
pub fn ty_option(t: ast::Type) -> ast::Type { pub fn ty_option(t: ast::Type) -> ast::Type {
ty_from_text(&format!("Option<{}>", t)) ty_from_text(&format!("Option<{t}>"))
} }
pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type { pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type {
ty_from_text(&format!("Result<{}, {}>", t, e)) ty_from_text(&format!("Result<{t}, {e}>"))
} }
} }
pub fn name(text: &str) -> ast::Name { pub fn name(name: &str) -> ast::Name {
ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text)) let raw_escape = raw_ident_esc(name);
ast_from_text(&format!("mod {raw_escape}{name};"))
} }
pub fn name_ref(text: &str) -> ast::NameRef { pub fn name_ref(name_ref: &str) -> ast::NameRef {
ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text)) let raw_escape = raw_ident_esc(name_ref);
ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}"))
} }
fn raw_ident_esc(ident: &str) -> &'static str { fn raw_ident_esc(ident: &str) -> &'static str {
let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some(); let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some();
@ -118,10 +120,10 @@ pub fn lifetime(text: &str) -> ast::Lifetime {
let mut text = text; let mut text = text;
let tmp; let tmp;
if never!(!text.starts_with('\'')) { if never!(!text.starts_with('\'')) {
tmp = format!("'{}", text); tmp = format!("'{text}");
text = &tmp; text = &tmp;
} }
ast_from_text(&format!("fn f<{}>() {{ }}", text)) ast_from_text(&format!("fn f<{text}>() {{ }}"))
} }
// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la // FIXME: replace stringly-typed constructor with a family of typed ctors, a-la
@ -142,16 +144,16 @@ pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
contents.push(','); contents.push(',');
} }
ty_from_text(&format!("({})", contents)) ty_from_text(&format!("({contents})"))
} }
pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type { pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type {
ty_from_text(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) }) ty_from_text(&if exclusive { format!("&mut {target}") } else { format!("&{target}") })
} }
pub fn ty_path(path: ast::Path) -> ast::Type { pub fn ty_path(path: ast::Path) -> ast::Type {
ty_from_text(&path.to_string()) ty_from_text(&path.to_string())
} }
fn ty_from_text(text: &str) -> ast::Type { fn ty_from_text(text: &str) -> ast::Type {
ast_from_text(&format!("type _T = {};", text)) ast_from_text(&format!("type _T = {text};"))
} }
pub fn assoc_item_list() -> ast::AssocItemList { pub fn assoc_item_list() -> ast::AssocItemList {
@ -171,7 +173,7 @@ pub fn impl_(
Some(params) => params.to_string(), Some(params) => params.to_string(),
None => String::new(), None => String::new(),
}; };
ast_from_text(&format!("impl{} {}{} {{}}", params, ty, ty_params)) ast_from_text(&format!("impl{params} {ty}{ty_params} {{}}"))
} }
pub fn impl_trait( pub fn impl_trait(
@ -180,7 +182,7 @@ pub fn impl_trait(
ty_params: Option<ast::GenericParamList>, ty_params: Option<ast::GenericParamList>,
) -> ast::Impl { ) -> ast::Impl {
let ty_params = ty_params.map_or_else(String::new, |params| params.to_string()); let ty_params = ty_params.map_or_else(String::new, |params| params.to_string());
ast_from_text(&format!("impl{2} {} for {}{2} {{}}", trait_, ty, ty_params)) ast_from_text(&format!("impl{ty_params} {trait_} for {ty}{ty_params} {{}}"))
} }
pub(crate) fn generic_arg_list() -> ast::GenericArgList { pub(crate) fn generic_arg_list() -> ast::GenericArgList {
@ -188,13 +190,13 @@ pub(crate) fn generic_arg_list() -> ast::GenericArgList {
} }
pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
ast_from_text(&format!("type __ = {};", name_ref)) ast_from_text(&format!("type __ = {name_ref};"))
} }
pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option<ast::PathType>) -> ast::PathSegment { pub fn path_segment_ty(type_ref: ast::Type, trait_ref: Option<ast::PathType>) -> ast::PathSegment {
let text = match trait_ref { let text = match trait_ref {
Some(trait_ref) => format!("fn f(x: <{} as {}>) {{}}", type_ref, trait_ref), Some(trait_ref) => format!("fn f(x: <{type_ref} as {trait_ref}>) {{}}"),
None => format!("fn f(x: <{}>) {{}}", type_ref), None => format!("fn f(x: <{type_ref}>) {{}}"),
}; };
ast_from_text(&text) ast_from_text(&text)
} }
@ -212,15 +214,15 @@ pub fn path_segment_crate() -> ast::PathSegment {
} }
pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
ast_from_text(&format!("type __ = {};", segment)) ast_from_text(&format!("type __ = {segment};"))
} }
pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
ast_from_text(&format!("{}::{}", qual, segment)) ast_from_text(&format!("{qual}::{segment}"))
} }
// FIXME: path concatenation operation doesn't make sense as AST op. // FIXME: path concatenation operation doesn't make sense as AST op.
pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path { pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path {
ast_from_text(&format!("type __ = {}::{};", first, second)) ast_from_text(&format!("type __ = {first}::{second};"))
} }
pub fn path_from_segments( pub fn path_from_segments(
@ -229,20 +231,20 @@ pub fn path_from_segments(
) -> ast::Path { ) -> ast::Path {
let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::"); let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::");
ast_from_text(&if is_abs { ast_from_text(&if is_abs {
format!("fn f(x: ::{}) {{}}", segments) format!("fn f(x: ::{segments}) {{}}")
} else { } else {
format!("fn f(x: {}) {{}}", segments) format!("fn f(x: {segments}) {{}}")
}) })
} }
pub fn join_paths(paths: impl IntoIterator<Item = ast::Path>) -> ast::Path { pub fn join_paths(paths: impl IntoIterator<Item = ast::Path>) -> ast::Path {
let paths = paths.into_iter().map(|it| it.syntax().clone()).join("::"); let paths = paths.into_iter().map(|it| it.syntax().clone()).join("::");
ast_from_text(&format!("type __ = {};", paths)) ast_from_text(&format!("type __ = {paths};"))
} }
// FIXME: should not be pub // FIXME: should not be pub
pub fn path_from_text(text: &str) -> ast::Path { pub fn path_from_text(text: &str) -> ast::Path {
ast_from_text(&format!("fn main() {{ let test = {}; }}", text)) ast_from_text(&format!("fn main() {{ let test = {text}; }}"))
} }
pub fn use_tree_glob() -> ast::UseTree { pub fn use_tree_glob() -> ast::UseTree {
@ -257,50 +259,50 @@ pub fn use_tree(
let mut buf = "use ".to_string(); let mut buf = "use ".to_string();
buf += &path.syntax().to_string(); buf += &path.syntax().to_string();
if let Some(use_tree_list) = use_tree_list { if let Some(use_tree_list) = use_tree_list {
format_to!(buf, "::{}", use_tree_list); format_to!(buf, "::{use_tree_list}");
} }
if add_star { if add_star {
buf += "::*"; buf += "::*";
} }
if let Some(alias) = alias { if let Some(alias) = alias {
format_to!(buf, " {}", alias); format_to!(buf, " {alias}");
} }
ast_from_text(&buf) ast_from_text(&buf)
} }
pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList { pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast::UseTreeList {
let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", "); let use_trees = use_trees.into_iter().map(|it| it.syntax().clone()).join(", ");
ast_from_text(&format!("use {{{}}};", use_trees)) ast_from_text(&format!("use {{{use_trees}}};"))
} }
pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use { pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use {
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
ast_from_text(&format!("{}use {};", visibility, use_tree)) ast_from_text(&format!("{visibility}use {use_tree};"))
} }
pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr { pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
ast_from_text(&format!("fn f() {{ {} {} }}", path, fields)) ast_from_text(&format!("fn f() {{ {path} {fields} }}"))
} }
pub fn record_expr_field_list( pub fn record_expr_field_list(
fields: impl IntoIterator<Item = ast::RecordExprField>, fields: impl IntoIterator<Item = ast::RecordExprField>,
) -> ast::RecordExprFieldList { ) -> ast::RecordExprFieldList {
let fields = fields.into_iter().join(", "); let fields = fields.into_iter().join(", ");
ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields)) ast_from_text(&format!("fn f() {{ S {{ {fields} }} }}"))
} }
pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
return match expr { return match expr {
Some(expr) => from_text(&format!("{}: {}", name, expr)), Some(expr) => from_text(&format!("{name}: {expr}")),
None => from_text(&name.to_string()), None => from_text(&name.to_string()),
}; };
fn from_text(text: &str) -> ast::RecordExprField { fn from_text(text: &str) -> ast::RecordExprField {
ast_from_text(&format!("fn f() {{ S {{ {}, }} }}", text)) ast_from_text(&format!("fn f() {{ S {{ {text}, }} }}"))
} }
} }
@ -311,9 +313,9 @@ pub fn record_field(
) -> ast::RecordField { ) -> ast::RecordField {
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
ast_from_text(&format!("struct S {{ {}{}: {}, }}", visibility, name, ty)) ast_from_text(&format!("struct S {{ {visibility}{name}: {ty}, }}"))
} }
// TODO // TODO
@ -323,13 +325,13 @@ pub fn block_expr(
) -> ast::BlockExpr { ) -> ast::BlockExpr {
let mut buf = "{\n".to_string(); let mut buf = "{\n".to_string();
for stmt in stmts.into_iter() { for stmt in stmts.into_iter() {
format_to!(buf, " {}\n", stmt); format_to!(buf, " {stmt}\n");
} }
if let Some(tail_expr) = tail_expr { if let Some(tail_expr) = tail_expr {
format_to!(buf, " {}\n", tail_expr); format_to!(buf, " {tail_expr}\n");
} }
buf += "}"; buf += "}";
ast_from_text(&format!("fn f() {}", buf)) ast_from_text(&format!("fn f() {buf}"))
} }
/// Ideally this function wouldn't exist since it involves manual indenting. /// Ideally this function wouldn't exist since it involves manual indenting.
@ -343,18 +345,18 @@ pub fn hacky_block_expr_with_comments(
let mut buf = "{\n".to_string(); let mut buf = "{\n".to_string();
for node_or_token in elements.into_iter() { for node_or_token in elements.into_iter() {
match node_or_token { match node_or_token {
rowan::NodeOrToken::Node(n) => format_to!(buf, " {}\n", n), rowan::NodeOrToken::Node(n) => format_to!(buf, " {n}\n"),
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::COMMENT => { rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::COMMENT => {
format_to!(buf, " {}\n", t) format_to!(buf, " {t}\n")
} }
_ => (), _ => (),
} }
} }
if let Some(tail_expr) = tail_expr { if let Some(tail_expr) = tail_expr {
format_to!(buf, " {}\n", tail_expr); format_to!(buf, " {tail_expr}\n");
} }
buf += "}"; buf += "}";
ast_from_text(&format!("fn f() {}", buf)) ast_from_text(&format!("fn f() {buf}"))
} }
pub fn expr_unit() -> ast::Expr { pub fn expr_unit() -> ast::Expr {
@ -362,7 +364,7 @@ pub fn expr_unit() -> ast::Expr {
} }
pub fn expr_literal(text: &str) -> ast::Literal { pub fn expr_literal(text: &str) -> ast::Literal {
assert_eq!(text.trim(), text); assert_eq!(text.trim(), text);
ast_from_text(&format!("fn f() {{ let _ = {}; }}", text)) ast_from_text(&format!("fn f() {{ let _ = {text}; }}"))
} }
pub fn expr_empty_block() -> ast::Expr { pub fn expr_empty_block() -> ast::Expr {
@ -373,41 +375,41 @@ pub fn expr_path(path: ast::Path) -> ast::Expr {
} }
pub fn expr_continue(label: Option<ast::Lifetime>) -> ast::Expr { pub fn expr_continue(label: Option<ast::Lifetime>) -> ast::Expr {
match label { match label {
Some(label) => expr_from_text(&format!("continue {}", label)), Some(label) => expr_from_text(&format!("continue {label}")),
None => expr_from_text("continue"), None => expr_from_text("continue"),
} }
} }
// Consider `op: SyntaxKind` instead for nicer syntax at the call-site? // Consider `op: SyntaxKind` instead for nicer syntax at the call-site?
pub fn expr_bin_op(lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { pub fn expr_bin_op(lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{} {} {}", lhs, op, rhs)) expr_from_text(&format!("{lhs} {op} {rhs}"))
} }
pub fn expr_break(label: Option<ast::Lifetime>, expr: Option<ast::Expr>) -> ast::Expr { pub fn expr_break(label: Option<ast::Lifetime>, expr: Option<ast::Expr>) -> ast::Expr {
let mut s = String::from("break"); let mut s = String::from("break");
if let Some(label) = label { if let Some(label) = label {
format_to!(s, " {}", label); format_to!(s, " {label}");
} }
if let Some(expr) = expr { if let Some(expr) = expr {
format_to!(s, " {}", expr); format_to!(s, " {expr}");
} }
expr_from_text(&s) expr_from_text(&s)
} }
pub fn expr_return(expr: Option<ast::Expr>) -> ast::Expr { pub fn expr_return(expr: Option<ast::Expr>) -> ast::Expr {
match expr { match expr {
Some(expr) => expr_from_text(&format!("return {}", expr)), Some(expr) => expr_from_text(&format!("return {expr}")),
None => expr_from_text("return"), None => expr_from_text("return"),
} }
} }
pub fn expr_try(expr: ast::Expr) -> ast::Expr { pub fn expr_try(expr: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{}?", expr)) expr_from_text(&format!("{expr}?"))
} }
pub fn expr_await(expr: ast::Expr) -> ast::Expr { pub fn expr_await(expr: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{}.await", expr)) expr_from_text(&format!("{expr}.await"))
} }
pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr { pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
expr_from_text(&format!("match {} {}", expr, match_arm_list)) expr_from_text(&format!("match {expr} {match_arm_list}"))
} }
pub fn expr_if( pub fn expr_if(
condition: ast::Expr, condition: ast::Expr,
@ -415,66 +417,67 @@ pub fn expr_if(
else_branch: Option<ast::ElseBranch>, else_branch: Option<ast::ElseBranch>,
) -> ast::Expr { ) -> ast::Expr {
let else_branch = match else_branch { let else_branch = match else_branch {
Some(ast::ElseBranch::Block(block)) => format!("else {}", block), Some(ast::ElseBranch::Block(block)) => format!("else {block}"),
Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr), Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"),
None => String::new(), None => String::new(),
}; };
expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch)) expr_from_text(&format!("if {condition} {then_branch} {else_branch}"))
} }
pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr { pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr {
expr_from_text(&format!("for {} in {} {}", pat, expr, block)) expr_from_text(&format!("for {pat} in {expr} {block}"))
} }
pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr {
expr_from_text(&format!("loop {}", block)) expr_from_text(&format!("loop {block}"))
} }
pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
let token = token(op); let token = token(op);
expr_from_text(&format!("{}{}", token, expr)) expr_from_text(&format!("{token}{expr}"))
} }
pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
expr_from_text(&format!("{}{}", f, arg_list)) expr_from_text(&format!("{f}{arg_list}"))
} }
pub fn expr_method_call( pub fn expr_method_call(
receiver: ast::Expr, receiver: ast::Expr,
method: ast::NameRef, method: ast::NameRef,
arg_list: ast::ArgList, arg_list: ast::ArgList,
) -> ast::Expr { ) -> ast::Expr {
expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) expr_from_text(&format!("{receiver}.{method}{arg_list}"))
} }
pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { pub fn expr_macro_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr {
expr_from_text(&format!("{}!{}", f, arg_list)) expr_from_text(&format!("{f}!{arg_list}"))
} }
pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr {
expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) }) expr_from_text(&if exclusive { format!("&mut {expr}") } else { format!("&{expr}") })
} }
pub fn expr_closure(pats: impl IntoIterator<Item = ast::Param>, expr: ast::Expr) -> ast::Expr { pub fn expr_closure(pats: impl IntoIterator<Item = ast::Param>, expr: ast::Expr) -> ast::Expr {
let params = pats.into_iter().join(", "); let params = pats.into_iter().join(", ");
expr_from_text(&format!("|{}| {}", params, expr)) expr_from_text(&format!("|{params}| {expr}"))
} }
pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr { pub fn expr_field(receiver: ast::Expr, field: &str) -> ast::Expr {
expr_from_text(&format!("{}.{}", receiver, field)) expr_from_text(&format!("{receiver}.{field}"))
} }
pub fn expr_paren(expr: ast::Expr) -> ast::Expr { pub fn expr_paren(expr: ast::Expr) -> ast::Expr {
expr_from_text(&format!("({})", expr)) expr_from_text(&format!("({expr})"))
} }
pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr { pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
let expr = elements.into_iter().format(", "); let expr = elements.into_iter().format(", ");
expr_from_text(&format!("({})", expr)) expr_from_text(&format!("({expr})"))
} }
pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
expr_from_text(&format!("{} = {}", lhs, rhs)) expr_from_text(&format!("{lhs} = {rhs}"))
} }
fn expr_from_text(text: &str) -> ast::Expr { fn expr_from_text(text: &str) -> ast::Expr {
ast_from_text(&format!("const C: () = {};", text)) ast_from_text(&format!("const C: () = {text};"))
} }
pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr {
ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr)) ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};"))
} }
pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList { pub fn arg_list(args: impl IntoIterator<Item = ast::Expr>) -> ast::ArgList {
ast_from_text(&format!("fn main() {{ ()({}) }}", args.into_iter().format(", "))) let args = args.into_iter().format(", ");
ast_from_text(&format!("fn main() {{ ()({args}) }}"))
} }
pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
@ -485,7 +488,7 @@ pub fn ident_pat(ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
if mut_ { if mut_ {
s.push_str("mut "); s.push_str("mut ");
} }
format_to!(s, "{}", name); format_to!(s, "{name}");
s.push_str(": ())"); s.push_str(": ())");
ast_from_text(&s) ast_from_text(&s)
} }
@ -494,7 +497,7 @@ pub fn wildcard_pat() -> ast::WildcardPat {
return from_text("_"); return from_text("_");
fn from_text(text: &str) -> ast::WildcardPat { fn from_text(text: &str) -> ast::WildcardPat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
@ -502,7 +505,7 @@ pub fn literal_pat(lit: &str) -> ast::LiteralPat {
return from_text(lit); return from_text(lit);
fn from_text(text: &str) -> ast::LiteralPat { fn from_text(text: &str) -> ast::LiteralPat {
ast_from_text(&format!("fn f() {{ match x {{ {} => {{}} }} }}", text)) ast_from_text(&format!("fn f() {{ match x {{ {text} => {{}} }} }}"))
} }
} }
@ -515,10 +518,10 @@ pub fn tuple_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::TuplePat {
if count == 1 { if count == 1 {
pats_str.push(','); pats_str.push(',');
} }
return from_text(&format!("({})", pats_str)); return from_text(&format!("({pats_str})"));
fn from_text(text: &str) -> ast::TuplePat { fn from_text(text: &str) -> ast::TuplePat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
@ -527,46 +530,46 @@ pub fn tuple_struct_pat(
pats: impl IntoIterator<Item = ast::Pat>, pats: impl IntoIterator<Item = ast::Pat>,
) -> ast::TupleStructPat { ) -> ast::TupleStructPat {
let pats_str = pats.into_iter().join(", "); let pats_str = pats.into_iter().join(", ");
return from_text(&format!("{}({})", path, pats_str)); return from_text(&format!("{path}({pats_str})"));
fn from_text(text: &str) -> ast::TupleStructPat { fn from_text(text: &str) -> ast::TupleStructPat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat { pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat {
let pats_str = pats.into_iter().join(", "); let pats_str = pats.into_iter().join(", ");
return from_text(&format!("{} {{ {} }}", path, pats_str)); return from_text(&format!("{path} {{ {pats_str} }}"));
fn from_text(text: &str) -> ast::RecordPat { fn from_text(text: &str) -> ast::RecordPat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat { pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
ast_from_text(&format!("fn f({} {}: ()))", path, fields)) ast_from_text(&format!("fn f({path} {fields}: ()))"))
} }
pub fn record_pat_field_list( pub fn record_pat_field_list(
fields: impl IntoIterator<Item = ast::RecordPatField>, fields: impl IntoIterator<Item = ast::RecordPatField>,
) -> ast::RecordPatFieldList { ) -> ast::RecordPatFieldList {
let fields = fields.into_iter().join(", "); let fields = fields.into_iter().join(", ");
ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields)) ast_from_text(&format!("fn f(S {{ {fields} }}: ()))"))
} }
pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField { pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat)) ast_from_text(&format!("fn f(S {{ {name_ref}: {pat} }}: ()))"))
} }
pub fn record_pat_field_shorthand(name_ref: ast::NameRef) -> ast::RecordPatField { pub fn record_pat_field_shorthand(name_ref: ast::NameRef) -> ast::RecordPatField {
ast_from_text(&format!("fn f(S {{ {} }}: ()))", name_ref)) ast_from_text(&format!("fn f(S {{ {name_ref} }}: ()))"))
} }
/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. /// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
pub fn path_pat(path: ast::Path) -> ast::Pat { pub fn path_pat(path: ast::Path) -> ast::Pat {
return from_text(&path.to_string()); return from_text(&path.to_string());
fn from_text(text: &str) -> ast::Pat { fn from_text(text: &str) -> ast::Pat {
ast_from_text(&format!("fn f({}: ())", text)) ast_from_text(&format!("fn f({text}: ())"))
} }
} }
@ -577,12 +580,12 @@ pub fn match_arm(
) -> ast::MatchArm { ) -> ast::MatchArm {
let pats_str = pats.into_iter().join(" | "); let pats_str = pats.into_iter().join(" | ");
return match guard { return match guard {
Some(guard) => from_text(&format!("{} if {} => {}", pats_str, guard, expr)), Some(guard) => from_text(&format!("{pats_str} if {guard} => {expr}")),
None => from_text(&format!("{} => {}", pats_str, expr)), None => from_text(&format!("{pats_str} => {expr}")),
}; };
fn from_text(text: &str) -> ast::MatchArm { fn from_text(text: &str) -> ast::MatchArm {
ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text)) ast_from_text(&format!("fn f() {{ match () {{{text}}} }}"))
} }
} }
@ -592,10 +595,10 @@ pub fn match_arm_with_guard(
expr: ast::Expr, expr: ast::Expr,
) -> ast::MatchArm { ) -> ast::MatchArm {
let pats_str = pats.into_iter().join(" | "); let pats_str = pats.into_iter().join(" | ");
return from_text(&format!("{} if {} => {}", pats_str, guard, expr)); return from_text(&format!("{pats_str} if {guard} => {expr}"));
fn from_text(text: &str) -> ast::MatchArm { fn from_text(text: &str) -> ast::MatchArm {
ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text)) ast_from_text(&format!("fn f() {{ match () {{{text}}} }}"))
} }
} }
@ -605,13 +608,14 @@ pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::Mat
.map(|arm| { .map(|arm| {
let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like()); let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like());
let comma = if needs_comma { "," } else { "" }; let comma = if needs_comma { "," } else { "" };
format!(" {}{}\n", arm.syntax(), comma) let arm = arm.syntax();
format!(" {arm}{comma}\n")
}) })
.collect::<String>(); .collect::<String>();
return from_text(&arms_str); return from_text(&arms_str);
fn from_text(text: &str) -> ast::MatchArmList { fn from_text(text: &str) -> ast::MatchArmList {
ast_from_text(&format!("fn f() {{ match () {{\n{}}} }}", text)) ast_from_text(&format!("fn f() {{ match () {{\n{text}}} }}"))
} }
} }
@ -620,10 +624,10 @@ pub fn where_pred(
bounds: impl IntoIterator<Item = ast::TypeBound>, bounds: impl IntoIterator<Item = ast::TypeBound>,
) -> ast::WherePred { ) -> ast::WherePred {
let bounds = bounds.into_iter().join(" + "); let bounds = bounds.into_iter().join(" + ");
return from_text(&format!("{}: {}", path, bounds)); return from_text(&format!("{path}: {bounds}"));
fn from_text(text: &str) -> ast::WherePred { fn from_text(text: &str) -> ast::WherePred {
ast_from_text(&format!("fn f() where {} {{ }}", text)) ast_from_text(&format!("fn f() where {text} {{ }}"))
} }
} }
@ -632,7 +636,7 @@ pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::Whe
return from_text(preds.as_str()); return from_text(preds.as_str());
fn from_text(text: &str) -> ast::WhereClause { fn from_text(text: &str) -> ast::WhereClause {
ast_from_text(&format!("fn f() where {} {{ }}", text)) ast_from_text(&format!("fn f() where {text} {{ }}"))
} }
} }
@ -642,19 +646,19 @@ pub fn let_stmt(
initializer: Option<ast::Expr>, initializer: Option<ast::Expr>,
) -> ast::LetStmt { ) -> ast::LetStmt {
let mut text = String::new(); let mut text = String::new();
format_to!(text, "let {}", pattern); format_to!(text, "let {pattern}");
if let Some(ty) = ty { if let Some(ty) = ty {
format_to!(text, ": {}", ty); format_to!(text, ": {ty}");
} }
match initializer { match initializer {
Some(it) => format_to!(text, " = {};", it), Some(it) => format_to!(text, " = {it};"),
None => format_to!(text, ";"), None => format_to!(text, ";"),
}; };
ast_from_text(&format!("fn f() {{ {} }}", text)) ast_from_text(&format!("fn f() {{ {text} }}"))
} }
pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt { pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
let semi = if expr.is_block_like() { "" } else { ";" }; let semi = if expr.is_block_like() { "" } else { ";" };
ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi)) ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}"))
} }
pub fn item_const( pub fn item_const(
@ -665,13 +669,13 @@ pub fn item_const(
) -> ast::Const { ) -> ast::Const {
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
ast_from_text(&format!("{} const {}: {} = {};", visibility, name, ty, expr)) ast_from_text(&format!("{visibility} const {name}: {ty} = {expr};"))
} }
pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty)) ast_from_text(&format!("fn f({pat}: {ty}) {{ }}"))
} }
pub fn self_param() -> ast::SelfParam { pub fn self_param() -> ast::SelfParam {
@ -679,7 +683,7 @@ pub fn self_param() -> ast::SelfParam {
} }
pub fn ret_type(ty: ast::Type) -> ast::RetType { pub fn ret_type(ty: ast::Type) -> ast::RetType {
ast_from_text(&format!("fn f() -> {} {{ }}", ty)) ast_from_text(&format!("fn f() -> {ty} {{ }}"))
} }
pub fn param_list( pub fn param_list(
@ -688,30 +692,30 @@ pub fn param_list(
) -> ast::ParamList { ) -> ast::ParamList {
let args = pats.into_iter().join(", "); let args = pats.into_iter().join(", ");
let list = match self_param { let list = match self_param {
Some(self_param) if args.is_empty() => format!("fn f({}) {{ }}", self_param), Some(self_param) if args.is_empty() => format!("fn f({self_param}) {{ }}"),
Some(self_param) => format!("fn f({}, {}) {{ }}", self_param, args), Some(self_param) => format!("fn f({self_param}, {args}) {{ }}"),
None => format!("fn f({}) {{ }}", args), None => format!("fn f({args}) {{ }}"),
}; };
ast_from_text(&list) ast_from_text(&list)
} }
pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam { pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam {
let bound = match ty { let bound = match ty {
Some(it) => format!(": {}", it), Some(it) => format!(": {it}"),
None => String::new(), None => String::new(),
}; };
ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound)) ast_from_text(&format!("fn f<{name}{bound}>() {{ }}"))
} }
pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam { pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam {
ast_from_text(&format!("fn f<{}>() {{ }}", lifetime)) ast_from_text(&format!("fn f<{lifetime}>() {{ }}"))
} }
pub fn generic_param_list( pub fn generic_param_list(
pats: impl IntoIterator<Item = ast::GenericParam>, pats: impl IntoIterator<Item = ast::GenericParam>,
) -> ast::GenericParamList { ) -> ast::GenericParamList {
let args = pats.into_iter().join(", "); let args = pats.into_iter().join(", ");
ast_from_text(&format!("fn f<{}>() {{ }}", args)) ast_from_text(&format!("fn f<{args}>() {{ }}"))
} }
pub fn visibility_pub_crate() -> ast::Visibility { pub fn visibility_pub_crate() -> ast::Visibility {
@ -724,33 +728,33 @@ pub fn visibility_pub() -> ast::Visibility {
pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList { pub fn tuple_field_list(fields: impl IntoIterator<Item = ast::TupleField>) -> ast::TupleFieldList {
let fields = fields.into_iter().join(", "); let fields = fields.into_iter().join(", ");
ast_from_text(&format!("struct f({});", fields)) ast_from_text(&format!("struct f({fields});"))
} }
pub fn record_field_list( pub fn record_field_list(
fields: impl IntoIterator<Item = ast::RecordField>, fields: impl IntoIterator<Item = ast::RecordField>,
) -> ast::RecordFieldList { ) -> ast::RecordFieldList {
let fields = fields.into_iter().join(", "); let fields = fields.into_iter().join(", ");
ast_from_text(&format!("struct f {{ {} }}", fields)) ast_from_text(&format!("struct f {{ {fields} }}"))
} }
pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField { pub fn tuple_field(visibility: Option<ast::Visibility>, ty: ast::Type) -> ast::TupleField {
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
ast_from_text(&format!("struct f({}{});", visibility, ty)) ast_from_text(&format!("struct f({visibility}{ty});"))
} }
pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant { pub fn variant(name: ast::Name, field_list: Option<ast::FieldList>) -> ast::Variant {
let field_list = match field_list { let field_list = match field_list {
None => String::new(), None => String::new(),
Some(it) => match it { Some(it) => match it {
ast::FieldList::RecordFieldList(record) => format!(" {}", record), ast::FieldList::RecordFieldList(record) => format!(" {record}"),
ast::FieldList::TupleFieldList(tuple) => format!("{}", tuple), ast::FieldList::TupleFieldList(tuple) => format!("{tuple}"),
}, },
}; };
ast_from_text(&format!("enum f {{ {}{} }}", name, field_list)) ast_from_text(&format!("enum f {{ {name}{field_list} }}"))
} }
pub fn fn_( pub fn fn_(
@ -763,23 +767,22 @@ pub fn fn_(
is_async: bool, is_async: bool,
) -> ast::Fn { ) -> ast::Fn {
let type_params = match type_params { let type_params = match type_params {
Some(type_params) => format!("{}", type_params), Some(type_params) => format!("{type_params}"),
None => "".into(), None => "".into(),
}; };
let ret_type = match ret_type { let ret_type = match ret_type {
Some(ret_type) => format!("{} ", ret_type), Some(ret_type) => format!("{ret_type} "),
None => "".into(), None => "".into(),
}; };
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
let async_literal = if is_async { "async " } else { "" }; let async_literal = if is_async { "async " } else { "" };
ast_from_text(&format!( ast_from_text(&format!(
"{}{}fn {}{}{} {}{}", "{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{body}",
visibility, async_literal, fn_name, type_params, params, ret_type, body
)) ))
} }
@ -793,13 +796,10 @@ pub fn struct_(
let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string()); let type_params = generic_param_list.map_or_else(String::new, |it| it.to_string());
let visibility = match visibility { let visibility = match visibility {
None => String::new(), None => String::new(),
Some(it) => format!("{} ", it), Some(it) => format!("{it} "),
}; };
ast_from_text(&format!( ast_from_text(&format!("{visibility}struct {strukt_name}{type_params}{field_list}{semicolon}",))
"{}struct {}{}{}{}",
visibility, strukt_name, type_params, field_list, semicolon
))
} }
#[track_caller] #[track_caller]
@ -808,7 +808,8 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
let node = match parse.tree().syntax().descendants().find_map(N::cast) { let node = match parse.tree().syntax().descendants().find_map(N::cast) {
Some(it) => it, Some(it) => it,
None => { None => {
panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text) let node = std::any::type_name::<N>();
panic!("Failed to make ast node `{node}` from text {text}")
} }
}; };
let node = node.clone_subtree(); let node = node.clone_subtree();
@ -824,7 +825,7 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
.descendants_with_tokens() .descendants_with_tokens()
.filter_map(|it| it.into_token()) .filter_map(|it| it.into_token())
.find(|it| it.kind() == kind) .find(|it| it.kind() == kind)
.unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) .unwrap_or_else(|| panic!("unhandled token: {kind:?}"))
} }
pub mod tokens { pub mod tokens {
@ -863,7 +864,7 @@ pub mod tokens {
pub fn literal(text: &str) -> SyntaxToken { pub fn literal(text: &str) -> SyntaxToken {
assert_eq!(text.trim(), text); assert_eq!(text.trim(), text);
let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {}; }}", text)); let lit: ast::Literal = super::ast_from_text(&format!("fn f() {{ let _ = {text}; }}"));
lit.syntax().first_child_or_token().unwrap().into_token().unwrap() lit.syntax().first_child_or_token().unwrap().into_token().unwrap()
} }

View file

@ -322,7 +322,7 @@ impl ast::IntNumber {
pub fn float_value(&self) -> Option<f64> { pub fn float_value(&self) -> Option<f64> {
let (_, text, _) = self.split_into_parts(); let (_, text, _) = self.split_into_parts();
text.parse::<f64>().ok() text.replace('_', "").parse::<f64>().ok()
} }
} }
@ -361,7 +361,7 @@ impl ast::FloatNumber {
pub fn value(&self) -> Option<f64> { pub fn value(&self) -> Option<f64> {
let (text, _) = self.split_into_parts(); let (text, _) = self.split_into_parts();
text.parse::<f64>().ok() text.replace('_', "").parse::<f64>().ok()
} }
} }
@ -397,6 +397,15 @@ mod tests {
assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.suffix(), expected.into()); assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.suffix(), expected.into());
} }
fn check_float_value(lit: &str, expected: impl Into<Option<f64>> + Copy) {
assert_eq!(FloatNumber { syntax: make::tokens::literal(lit) }.value(), expected.into());
assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.float_value(), expected.into());
}
fn check_int_value(lit: &str, expected: impl Into<Option<u128>>) {
assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.value(), expected.into());
}
#[test] #[test]
fn test_float_number_suffix() { fn test_float_number_suffix() {
check_float_suffix("123.0", None); check_float_suffix("123.0", None);
@ -437,6 +446,14 @@ mod tests {
check_string_value(r"\nfoobar", "\nfoobar"); check_string_value(r"\nfoobar", "\nfoobar");
check_string_value(r"C:\\Windows\\System32\\", "C:\\Windows\\System32\\"); check_string_value(r"C:\\Windows\\System32\\", "C:\\Windows\\System32\\");
} }
#[test]
fn test_value_underscores() {
check_float_value("3.141592653589793_f64", 3.141592653589793_f64);
check_float_value("1__0.__0__f32", 10.0);
check_int_value("0b__1_0_", 2);
check_int_value("1_1_1_1_1_1", 111111);
}
} }
impl ast::Char { impl ast::Char {

View file

@ -169,10 +169,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> String {
quote! { quote! {
impl AstNode for #name { impl AstNode for #name {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(kind, #(#kinds)|*)
#(#kinds)|* => true,
_ => false,
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() { let res = match syntax.kind() {
@ -253,10 +250,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> String {
} }
impl AstNode for #name { impl AstNode for #name {
fn can_cast(kind: SyntaxKind) -> bool { fn can_cast(kind: SyntaxKind) -> bool {
match kind { matches!(kind, #(#kinds)|*)
#(#kinds)|* => true,
_ => false,
}
} }
fn cast(syntax: SyntaxNode) -> Option<Self> { fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then(|| #name { syntax }) Self::can_cast(syntax.kind()).then(|| #name { syntax })
@ -410,24 +404,17 @@ fn generate_syntax_kinds(grammar: KindsSrc<'_>) -> String {
impl SyntaxKind { impl SyntaxKind {
pub fn is_keyword(self) -> bool { pub fn is_keyword(self) -> bool {
match self { matches!(self, #(#all_keywords)|*)
#(#all_keywords)|* => true,
_ => false,
}
} }
pub fn is_punct(self) -> bool { pub fn is_punct(self) -> bool {
match self {
#(#punctuation)|* => true, matches!(self, #(#punctuation)|*)
_ => false,
}
} }
pub fn is_literal(self) -> bool { pub fn is_literal(self) -> bool {
match self { matches!(self, #(#literals)|*)
#(#literals)|* => true,
_ => false,
}
} }
pub fn from_keyword(ident: &str) -> Option<SyntaxKind> { pub fn from_keyword(ident: &str) -> Option<SyntaxKind> {

View file

@ -322,6 +322,43 @@ impl<T> Arena<T> {
.map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value)) .map(|(idx, value)| (Idx::from_raw(RawIdx(idx as u32)), value))
} }
/// Returns an iterator over the arenas values.
///
/// ```
/// let mut arena = la_arena::Arena::new();
/// let idx1 = arena.alloc(20);
/// let idx2 = arena.alloc(40);
/// let idx3 = arena.alloc(60);
///
/// let mut iterator = arena.values();
/// assert_eq!(iterator.next(), Some(&20));
/// assert_eq!(iterator.next(), Some(&40));
/// assert_eq!(iterator.next(), Some(&60));
/// ```
pub fn values(&mut self) -> impl Iterator<Item = &T> + ExactSizeIterator + DoubleEndedIterator {
self.data.iter()
}
/// Returns an iterator over the arenas mutable values.
///
/// ```
/// let mut arena = la_arena::Arena::new();
/// let idx1 = arena.alloc(20);
///
/// assert_eq!(arena[idx1], 20);
///
/// let mut iterator = arena.values_mut();
/// *iterator.next().unwrap() = 10;
/// drop(iterator);
///
/// assert_eq!(arena[idx1], 10);
/// ```
pub fn values_mut(
&mut self,
) -> impl Iterator<Item = &mut T> + ExactSizeIterator + DoubleEndedIterator {
self.data.iter_mut()
}
/// Reallocates the arena to make it take up as little space as possible. /// Reallocates the arena to make it take up as little space as possible.
pub fn shrink_to_fit(&mut self) { pub fn shrink_to_fit(&mut self) {
self.data.shrink_to_fit(); self.data.shrink_to_fit();