Merge branch 'master' of https://github.com/rust-analyzer/rust-analyzer into feature/themes

This commit is contained in:
Seivan Heidari 2019-11-18 20:53:40 +01:00
commit 4bdb6351ac
25 changed files with 370 additions and 429 deletions

View File

@ -1,16 +1,23 @@
name: Continuous integration
on: [pull_request, push]
name: CI
on:
pull_request:
push:
branches:
- master
- staging
jobs:
rust-tests:
name: Rust tests
rust:
name: Rust
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
CARGO_INCREMENTAL: 0
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
@ -18,31 +25,32 @@ jobs:
profile: minimal
override: true
components: rustfmt, rust-src
- name: Generate lockfile
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cargo target cache
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Compile
uses: actions-rs/cargo@v1
with:
command: test
args: --no-run
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
- name: Prepare build directory for cache
run: |
find ./target/debug -maxdepth 1 -type f -delete && \
rm -fr ./target/debug/{deps,.fingerprint}/{*ra_*,*heavy_test*,*gen_lsp*,*thread_worker*} && \
rm -f ./target/.rustc_info.json
vscode-tests:
name: VS Code tests
type-script:
name: TypeScript
runs-on: ubuntu-latest
env:
CXX: g++-4.9
@ -50,25 +58,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Generate lockfile
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cargo target cache
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Install Nodejs
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install xvfb
run: sudo apt-get install xvfb
- run: npm ci
@ -77,10 +72,6 @@ jobs:
working-directory: ./editors/code
- run: xvfb-run --auto-servernum npm run travis
working-directory: ./editors/code
- name: Cleanup xvfb
uses: bcomnes/cleanup-xvfb@v1
- name: Prepare build directory for cache
run: |
find ./target/debug -maxdepth 1 -type f -delete && \
rm -fr ./target/debug/{deps,.fingerprint}/{*ra_*,*heavy_test*,*gen_lsp*,*thread_worker*} && \
rm -f ./target/.rustc_info.json

39
.github/workflows/rustdoc.yaml vendored Normal file
View File

@ -0,0 +1,39 @@
name: rustdoc
on:
push:
branches:
- master
jobs:
rustdoc:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
CARGO_INCREMENTAL: 0
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt, rust-src
- name: Build Documentation
uses: actions-rs/cargo@v1
with:
command: doc
args: --all --no-deps
- name: Deploy Docs
uses: peaceiris/actions-gh-pages@v2.6.0-rc0
env:
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./target/doc
with:
forceOrphan: true

View File

@ -1,64 +0,0 @@
cache: cargo
before_cache:
- find ./target/debug -maxdepth 1 -type f -delete
- rm -fr ./target/debug/{deps,.fingerprint}/{*ra_*,*heavy_test*,*gen_lsp*,*thread_worker*}
- rm -f ./target/.rustc_info.json
matrix:
include:
- name: "Rust Tests"
os: linux
dist: xenial
language: rust
rust: stable
script:
- rustup component add rustfmt
- rustup component add rust-src
- cargo test --no-run # let's measure compile time separately
- cargo test
env:
- RUSTFLAGS="-D warnings", CARGO_INCREMENTAL=0
- name: "Rust Docs"
os: linux
if: branch = master AND type = push
before_script:
- DEPLOY_DOCS=1
language: rust
rust: stable
script:
- cargo doc --all --no-deps
env:
- RUSTFLAGS="-D warnings", CARGO_INCREMENTAL=0
- name: "VS Code Tests"
os: linux
language: node_js
dist: xenial
node_js: node
services:
- xvfb
before_install: cd editors/code
install:
- npm ci
- npm run vscode:prepublish
script:
- npm run travis
env:
- CXX="g++-4.9", CC="gcc-4.9"
branches:
only:
- staging
- master
- trying
deploy:
provider: pages
skip-cleanup: true
github-token: $DOCS_TOKEN # Set in the settings page of your repository, as a secure variable
keep-history: false
local-dir: target/doc
on:
branch: master
condition: $DEPLOY_DOCS = 1

View File

@ -1,7 +1,5 @@
# Rust Analyzer
[![Build Status](https://travis-ci.org/rust-analyzer/rust-analyzer.svg?branch=master)](https://travis-ci.org/rust-analyzer/rust-analyzer)
Rust Analyzer is an **experimental** modular compiler frontend for the Rust
language. It is a part of a larger rls-2.0 effort to create excellent IDE
support for Rust. If you want to get involved, check the rls-2.0 working group
@ -58,9 +56,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
## Quick Links
* Work List: https://paper.dropbox.com/doc/RLS-2.0-work-list--AZ3BgHKKCtqszbsi3gi6sjchAQ-42vbnxzuKq2lKwW0mkn8Y
* API docs: https://rust-analyzer.github.io/rust-analyzer/ra_ide_api/
* CI: https://travis-ci.org/rust-analyzer/rust-analyzer
## License

View File

@ -1,4 +1,2 @@
status = [
"continuous-integration/travis-ci/push",
]
status = ["Rust", "TypeScript"]
delete_merged_branches = true

View File

@ -59,10 +59,8 @@ pub trait AstDatabase: SourceDatabase {
fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId;
fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>;
fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>;
fn parse_macro(
&self,
macro_file: MacroFile,
) -> Option<(Parse<SyntaxNode>, Arc<mbe::RevTokenMap>)>;
fn parse_macro(&self, macro_file: MacroFile)
-> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>;
fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>;
}
@ -136,7 +134,7 @@ pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Optio
pub(crate) fn parse_macro(
db: &dyn AstDatabase,
macro_file: MacroFile,
) -> Option<(Parse<SyntaxNode>, Arc<mbe::RevTokenMap>)> {
) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
let _p = profile("parse_macro_query");
let macro_call_id = macro_file.macro_call_id;

View File

@ -20,7 +20,7 @@
use ra_syntax::{
algo,
ast::{self, AstNode},
SyntaxNode, SyntaxToken, TextRange, TextUnit,
SyntaxNode, SyntaxToken, TextUnit,
};
use crate::ast_id_map::FileAstId;
@ -79,22 +79,17 @@ pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
HirFileIdRepr::MacroFile(macro_file) => {
let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
let arg_start = loc.ast_id.to_node(db).token_tree()?.syntax().text_range().start();
let def_start =
loc.def.ast_id.to_node(db).token_tree()?.syntax().text_range().start();
let arg_tt = loc.ast_id.to_node(db).token_tree()?;
let def_tt = loc.def.ast_id.to_node(db).token_tree()?;
let macro_def = db.macro_def(loc.def)?;
let (parse, exp_map) = db.parse_macro(macro_file)?;
let expanded = Source::new(self, parse.syntax_node());
let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
let arg_start = (loc.ast_id.file_id, arg_start);
let def_start = (loc.def.ast_id.file_id, def_start);
Some(ExpansionInfo {
expanded,
arg_start,
def_start,
expanded: Source::new(self, parse.syntax_node()),
arg: Source::new(loc.ast_id.file_id, arg_tt),
def: Source::new(loc.ast_id.file_id, def_tt),
macro_arg,
macro_def,
exp_map,
@ -159,18 +154,19 @@ pub fn as_file(self, kind: MacroFileKind) -> HirFileId {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExpansionInfo {
expanded: Source<SyntaxNode>,
arg_start: (HirFileId, TextUnit),
def_start: (HirFileId, TextUnit),
arg: Source<ast::TokenTree>,
def: Source<ast::TokenTree>,
macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
exp_map: Arc<mbe::RevTokenMap>,
exp_map: Arc<mbe::TokenMap>,
}
impl ExpansionInfo {
pub fn map_token_down(&self, token: Source<&SyntaxToken>) -> Option<Source<SyntaxToken>> {
assert_eq!(token.file_id, self.arg_start.0);
let range = token.ast.text_range().checked_sub(self.arg_start.1)?;
assert_eq!(token.file_id, self.arg.file_id);
let range =
token.ast.text_range().checked_sub(self.arg.ast.syntax().text_range().start())?;
let token_id = self.macro_arg.1.token_by_range(range)?;
let token_id = self.macro_def.0.map_id_down(token_id);
@ -181,25 +177,22 @@ pub fn map_token_down(&self, token: Source<&SyntaxToken>) -> Option<Source<Synta
Some(self.expanded.with_ast(token))
}
// FIXME: a more correct signature would be
// `pub fn map_token_up(&self, token: Source<&SyntaxToken>) -> Option<Source<SyntaxToken>>`
pub fn find_range(&self, from: TextRange) -> Option<(HirFileId, TextRange)> {
let token_id = look_in_rev_map(&self.exp_map, from)?;
pub fn map_token_up(&self, token: Source<&SyntaxToken>) -> Option<Source<SyntaxToken>> {
let token_id = self.exp_map.token_by_range(token.ast.text_range())?;
let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
let (token_map, (file_id, start_offset)) = match origin {
mbe::Origin::Call => (&self.macro_arg.1, self.arg_start),
mbe::Origin::Def => (&self.macro_def.1, self.def_start),
let (token_map, tt) = match origin {
mbe::Origin::Call => (&self.macro_arg.1, &self.arg),
mbe::Origin::Def => (&self.macro_def.1, &self.def),
};
let range = token_map.relative_range_of(token_id)?;
return Some((file_id, range + start_offset));
fn look_in_rev_map(exp_map: &mbe::RevTokenMap, from: TextRange) -> Option<tt::TokenId> {
exp_map.ranges.iter().find(|&it| it.0.is_subrange(&from)).map(|it| it.1)
}
let range = token_map.range_by_token(token_id)?;
let token = algo::find_covering_element(
tt.ast.syntax(),
range + tt.ast.syntax().text_range().start(),
)
.into_token()?;
Some(tt.with_ast(token))
}
}

View File

@ -1,16 +1,17 @@
//! FIXME: write short doc here
use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource};
use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source};
use ra_db::{FileId, SourceDatabase};
use ra_syntax::{
ast::{self, DocCommentsOwner, NameOwner},
match_ast, AstNode, SmolStr,
SyntaxKind::{self, BIND_PAT},
SyntaxNode, TextRange,
TextRange,
};
use crate::{db::RootDatabase, expand::original_range, FileSymbol};
use super::short_label::ShortLabel;
use crate::{db::RootDatabase, FileSymbol};
/// `NavigationTarget` represents and element in the editor's UI which you can
/// click on to navigate to a particular piece of code.
@ -79,13 +80,13 @@ pub fn focus_range(&self) -> Option<TextRange> {
pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
if let Some(src) = module.declaration_source(db) {
let (file_id, text_range) = find_range_from_node(db, src.file_id, src.ast.syntax());
let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
return NavigationTarget::from_syntax(
file_id,
frange.file_id,
name,
None,
text_range,
src.ast.syntax(),
frange.range,
src.ast.syntax().kind(),
src.ast.doc_comment_text(),
src.ast.short_label(),
);
@ -140,22 +141,22 @@ pub(crate) fn debug_render(&self) -> String {
/// Allows `NavigationTarget` to be created from a `NameOwner`
pub(crate) fn from_named(
db: &RootDatabase,
file_id: hir::HirFileId,
node: &impl ast::NameOwner,
node: Source<&dyn ast::NameOwner>,
docs: Option<String>,
description: Option<String>,
) -> NavigationTarget {
//FIXME: use `_` instead of empty string
let name = node.name().map(|it| it.text().clone()).unwrap_or_default();
let focus_range = node.name().map(|it| find_range_from_node(db, file_id, it.syntax()).1);
let (file_id, full_range) = find_range_from_node(db, file_id, node.syntax());
let name = node.ast.name().map(|it| it.text().clone()).unwrap_or_default();
let focus_range =
node.ast.name().map(|it| original_range(db, node.with_ast(it.syntax())).range);
let frange = original_range(db, node.map(|it| it.syntax()));
NavigationTarget::from_syntax(
file_id,
frange.file_id,
name,
focus_range,
full_range,
node.syntax(),
frange.range,
node.ast.syntax().kind(),
docs,
description,
)
@ -166,14 +167,14 @@ fn from_syntax(
name: SmolStr,
focus_range: Option<TextRange>,
full_range: TextRange,
node: &SyntaxNode,
kind: SyntaxKind,
docs: Option<String>,
description: Option<String>,
) -> NavigationTarget {
NavigationTarget {
file_id,
name,
kind: node.kind(),
kind,
full_range,
focus_range,
container_name: None,
@ -218,8 +219,7 @@ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let src = self.source(db);
NavigationTarget::from_named(
db,
src.file_id,
&src.ast,
src.as_ref().map(|it| it as &dyn ast::NameOwner),
src.ast.doc_comment_text(),
src.ast.short_label(),
)
@ -230,29 +230,29 @@ impl ToNav for hir::Module {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let src = self.definition_source(db);
let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default();
match src.ast {
match &src.ast {
ModuleSource::SourceFile(node) => {
let (file_id, text_range) = find_range_from_node(db, src.file_id, node.syntax());
let frange = original_range(db, src.with_ast(node.syntax()));
NavigationTarget::from_syntax(
file_id,
frange.file_id,
name,
None,
text_range,
node.syntax(),
frange.range,
node.syntax().kind(),
None,
None,
)
}
ModuleSource::Module(node) => {
let (file_id, text_range) = find_range_from_node(db, src.file_id, node.syntax());
let frange = original_range(db, src.with_ast(node.syntax()));
NavigationTarget::from_syntax(
file_id,
frange.file_id,
name,
None,
text_range,
node.syntax(),
frange.range,
node.syntax().kind(),
node.doc_comment_text(),
node.short_label(),
)
@ -264,14 +264,14 @@ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
impl ToNav for hir::ImplBlock {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let src = self.source(db);
let (file_id, text_range) = find_range_from_node(db, src.file_id, src.ast.syntax());
let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
NavigationTarget::from_syntax(
file_id,
frange.file_id,
"impl".into(),
None,
text_range,
src.ast.syntax(),
frange.range,
src.ast.syntax().kind(),
None,
None,
)
@ -282,22 +282,21 @@ impl ToNav for hir::StructField {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let src = self.source(db);
match src.ast {
match &src.ast {
FieldSource::Named(it) => NavigationTarget::from_named(
db,
src.file_id,
&it,
src.with_ast(it),
it.doc_comment_text(),
it.short_label(),
),
FieldSource::Pos(it) => {
let (file_id, text_range) = find_range_from_node(db, src.file_id, it.syntax());
let frange = original_range(db, src.with_ast(it.syntax()));
NavigationTarget::from_syntax(
file_id,
frange.file_id,
"".into(),
None,
text_range,
it.syntax(),
frange.range,
it.syntax().kind(),
None,
None,
)
@ -310,7 +309,12 @@ impl ToNav for hir::MacroDef {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let src = self.source(db);
log::debug!("nav target {:#?}", src.ast.syntax());
NavigationTarget::from_named(db, src.file_id, &src.ast, src.ast.doc_comment_text(), None)
NavigationTarget::from_named(
db,
src.as_ref().map(|it| it as &dyn ast::NameOwner),
src.ast.doc_comment_text(),
None,
)
}
}
@ -360,21 +364,6 @@ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
}
}
fn find_range_from_node(
db: &RootDatabase,
src: hir::HirFileId,
node: &SyntaxNode,
) -> (FileId, TextRange) {
let text_range = node.text_range();
let (file_id, text_range) = src
.expansion_info(db)
.and_then(|expansion_info| expansion_info.find_range(text_range))
.unwrap_or((src, text_range));
// FIXME: handle recursive macro generated macro
(file_id.original_file(db), text_range)
}
pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<String> {
let parse = db.parse(symbol.file_id);
let node = symbol.ptr.to_node(parse.tree().syntax());

View File

@ -0,0 +1,59 @@
//! Utilities to work with files, produced by macros.
use std::iter::successors;
use hir::Source;
use ra_db::FileId;
use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken};
use crate::{db::RootDatabase, FileRange};
pub(crate) fn original_range(db: &RootDatabase, node: Source<&SyntaxNode>) -> FileRange {
let expansion = match node.file_id.expansion_info(db) {
None => {
return FileRange {
file_id: node.file_id.original_file(db),
range: node.ast.text_range(),
}
}
Some(it) => it,
};
// FIXME: the following completely wrong.
//
// *First*, we should try to map first and last tokens of node, and, if that
// fails, return the range of the overall macro expansions.
//
// *Second*, we should handle recurside macro expansions
let token = node
.ast
.descendants_with_tokens()
.filter_map(|it| it.into_token())
.find_map(|it| expansion.map_token_up(node.with_ast(&it)));
match token {
Some(it) => FileRange { file_id: it.file_id.original_file(db), range: it.ast.text_range() },
None => FileRange { file_id: node.file_id.original_file(db), range: node.ast.text_range() },
}
}
pub(crate) fn descend_into_macros(
db: &RootDatabase,
file_id: FileId,
token: SyntaxToken,
) -> Source<SyntaxToken> {
let src = Source::new(file_id.into(), token);
successors(Some(src), |token| {
let macro_call = token.ast.ancestors().find_map(ast::MacroCall::cast)?;
let tt = macro_call.token_tree()?;
if !token.ast.text_range().is_subrange(&tt.syntax().text_range()) {
return None;
}
let source_analyzer =
hir::SourceAnalyzer::new(db, token.with_ast(token.ast.parent()).as_ref(), None);
let exp = source_analyzer.expand(db, &macro_call)?;
exp.map_token_down(db, token.as_ref())
})
.last()
.unwrap()
}

View File

@ -1,16 +1,15 @@
//! FIXME: write short doc here
use std::iter::successors;
use hir::{db::AstDatabase, Source};
use ra_syntax::{
ast::{self, DocCommentsOwner},
match_ast, AstNode, SyntaxNode, SyntaxToken,
match_ast, AstNode, SyntaxNode,
};
use crate::{
db::RootDatabase,
display::{ShortLabel, ToNav},
expand::descend_into_macros,
references::{classify_name_ref, NameKind::*},
FilePosition, NavigationTarget, RangeInfo,
};
@ -19,7 +18,9 @@ pub(crate) fn goto_definition(
db: &RootDatabase,
position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
let token = descend_into_macros(db, position)?;
let file = db.parse_or_expand(position.file_id.into())?;
let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?;
let token = descend_into_macros(db, position.file_id, token);
let res = match_ast! {
match (token.ast.parent()) {
@ -39,24 +40,6 @@ pub(crate) fn goto_definition(
Some(res)
}
fn descend_into_macros(db: &RootDatabase, position: FilePosition) -> Option<Source<SyntaxToken>> {
let file = db.parse_or_expand(position.file_id.into())?;
let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?;
successors(Some(Source::new(position.file_id.into(), token)), |token| {
let macro_call = token.ast.ancestors().find_map(ast::MacroCall::cast)?;
let tt = macro_call.token_tree()?;
if !token.ast.text_range().is_subrange(&tt.syntax().text_range()) {
return None;
}
let source_analyzer =
hir::SourceAnalyzer::new(db, token.with_ast(token.ast.parent()).as_ref(), None);
let exp = source_analyzer.expand(db, &macro_call)?;
exp.map_token_down(db, token.as_ref())
})
.last()
}
#[derive(Debug)]
pub(crate) enum ReferenceResult {
Exact(NavigationTarget),
@ -137,8 +120,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::StructDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -146,8 +128,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::EnumDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -155,8 +136,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::EnumVariant(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -164,8 +144,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::FnDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -173,8 +152,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::TypeAliasDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -182,8 +160,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::ConstDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -191,8 +168,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::StaticDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -200,8 +176,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::TraitDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -209,8 +184,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::RecordFieldDef(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -218,8 +192,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::Module(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
it.short_label(),
))
@ -227,8 +200,7 @@ fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option<Navigati
ast::MacroCall(it) => {
Some(NavigationTarget::from_named(
db,
node.file_id,
&it,
node.with_ast(&it),
it.doc_comment_text(),
None,
))

View File

@ -193,7 +193,9 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
None
}
} else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
if let Some(name_kind) = classify_name(db, position.file_id, &name).map(|d| d.kind) {
if let Some(name_kind) =
classify_name(db, Source::new(position.file_id.into(), &name)).map(|d| d.kind)
{
let mut _b: bool = true;
res.extend(hover_text_from_name_kind(db, name_kind, &mut _b));
}

View File

@ -41,6 +41,7 @@
mod display;
mod inlay_hints;
mod wasm_shims;
mod expand;
#[cfg(test)]
mod marks;

View File

@ -110,7 +110,7 @@ fn find_name<'a>(
position: FilePosition,
) -> Option<RangeInfo<(String, NameDefinition)>> {
if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
let def = classify_name(db, position.file_id, &name)?;
let def = classify_name(db, Source::new(position.file_id.into(), &name))?;
let range = name.syntax().text_range();
return Some(RangeInfo::new(range, (name.text().to_string(), def)));
}

View File

@ -1,7 +1,6 @@
//! Functions that are used to classify an element from its definition or reference.
use hir::{FromSource, Module, ModuleSource, Path, PathResolution, Source, SourceAnalyzer};
use ra_db::FileId;
use ra_prof::profile;
use ra_syntax::{ast, match_ast, AstNode};
use test_utils::tested_by;
@ -12,19 +11,14 @@
};
use crate::db::RootDatabase;
pub(crate) fn classify_name(
db: &RootDatabase,
file_id: FileId,
name: &ast::Name,
) -> Option<NameDefinition> {
pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option<NameDefinition> {
let _p = profile("classify_name");
let parent = name.syntax().parent()?;
let file_id = file_id.into();
let parent = name.ast.syntax().parent()?;
match_ast! {
match parent {
ast::BindPat(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let local = hir::Local::from_source(db, src)?;
Some(NameDefinition {
visibility: None,
@ -34,7 +28,7 @@ pub(crate) fn classify_name(
},
ast::RecordFieldDef(it) => {
let ast = hir::FieldSource::Named(it);
let src = hir::Source { file_id, ast };
let src = name.with_ast(ast);
let field = hir::StructField::from_source(db, src)?;
Some(from_struct_field(db, field))
},
@ -42,42 +36,42 @@ pub(crate) fn classify_name(
let def = {
if !it.has_semi() {
let ast = hir::ModuleSource::Module(it);
let src = hir::Source { file_id, ast };
let src = name.with_ast(ast);
hir::Module::from_definition(db, src)
} else {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
hir::Module::from_declaration(db, src)
}
}?;
Some(from_module_def(db, def.into(), None))
},
ast::StructDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Struct::from_source(db, src)?;
Some(from_module_def(db, def.into(), None))
},
ast::EnumDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Enum::from_source(db, src)?;
Some(from_module_def(db, def.into(), None))
},
ast::TraitDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Trait::from_source(db, src)?;
Some(from_module_def(db, def.into(), None))
},
ast::StaticDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Static::from_source(db, src)?;
Some(from_module_def(db, def.into(), None))
},
ast::EnumVariant(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::EnumVariant::from_source(db, src)?;
Some(from_module_def(db, def.into(), None))
},
ast::FnDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Function::from_source(db, src)?;
if parent.parent().and_then(ast::ItemList::cast).is_some() {
Some(from_assoc_item(db, def.into()))
@ -86,7 +80,7 @@ pub(crate) fn classify_name(
}
},
ast::ConstDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::Const::from_source(db, src)?;
if parent.parent().and_then(ast::ItemList::cast).is_some() {
Some(from_assoc_item(db, def.into()))
@ -95,7 +89,7 @@ pub(crate) fn classify_name(
}
},
ast::TypeAliasDef(it) => {
let src = hir::Source { file_id, ast: it };
let src = name.with_ast(it);
let def = hir::TypeAlias::from_source(db, src)?;
if parent.parent().and_then(ast::ItemList::cast).is_some() {
Some(from_assoc_item(db, def.into()))
@ -104,11 +98,11 @@ pub(crate) fn classify_name(
}
},
ast::MacroCall(it) => {
let src = hir::Source { file_id, ast: it};
let src = name.with_ast(it);
let def = hir::MacroDef::from_source(db, src.clone())?;
let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax()));
let module = Module::from_definition(db, Source::new(file_id, module_src))?;
let module = Module::from_definition(db, src.with_ast(module_src))?;
Some(NameDefinition {
visibility: None,

View File

@ -94,7 +94,8 @@ fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
}
NAME => {
let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap();
let name_kind = classify_name(db, file_id, &name).map(|d| d.kind);
let name_kind =
classify_name(db, Source::new(file_id.into(), &name)).map(|d| d.kind);
if let Some(Local(local)) = &name_kind {
if let Some(name) = local.name(db) {

View File

@ -31,7 +31,7 @@ pub enum ExpandError {
}
pub use crate::syntax_bridge::{
ast_to_token_tree, syntax_node_to_token_tree, token_tree_to_syntax_node, RevTokenMap, TokenMap,
ast_to_token_tree, syntax_node_to_token_tree, token_tree_to_syntax_node, TokenMap,
};
/// This struct contains AST for a single `macro_rules` definition. What might

View File

@ -14,30 +14,22 @@
#[derive(Debug, PartialEq, Eq, Default)]
pub struct TokenMap {
/// Maps `tt::TokenId` to the *relative* source range.
tokens: Vec<TextRange>,
}
/// Maps relative range of the expanded syntax node to `tt::TokenId`
#[derive(Debug, PartialEq, Eq, Default)]
pub struct RevTokenMap {
pub ranges: Vec<(TextRange, tt::TokenId)>,
entries: Vec<(tt::TokenId, TextRange)>,
}
/// Convert the syntax tree (what user has written) to a `TokenTree` (what macro
/// will consume).
pub fn ast_to_token_tree(ast: &ast::TokenTree) -> Option<(tt::Subtree, TokenMap)> {
let mut token_map = TokenMap::default();
let node = ast.syntax();
let tt = convert_tt(&mut token_map, node.text_range().start(), node)?;
Some((tt, token_map))
syntax_node_to_token_tree(ast.syntax())
}
/// Convert the syntax node to a `TokenTree` (what macro
/// will consume).
pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> Option<(tt::Subtree, TokenMap)> {
let mut token_map = TokenMap::default();
let tt = convert_tt(&mut token_map, node.text_range().start(), node)?;
Some((tt, token_map))
let global_offset = node.text_range().start();
let mut c = Convertor { map: TokenMap::default(), global_offset, next_id: 0 };
let subtree = c.go(node)?;
Some((subtree, c.map))
}
// The following items are what `rustc` macro can be parsed into :
@ -55,7 +47,7 @@ pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> Option<(tt::Subtree, Toke
pub fn token_tree_to_syntax_node(
tt: &tt::Subtree,
fragment_kind: FragmentKind,
) -> Result<(Parse<SyntaxNode>, RevTokenMap), ExpandError> {
) -> Result<(Parse<SyntaxNode>, TokenMap), ExpandError> {
let tmp;
let tokens = match tt {
tt::Subtree { delimiter: tt::Delimiter::None, token_trees } => token_trees.as_slice(),
@ -78,31 +70,17 @@ pub fn token_tree_to_syntax_node(
impl TokenMap {
pub fn token_by_range(&self, relative_range: TextRange) -> Option<tt::TokenId> {
let (idx, _) =
self.tokens.iter().enumerate().find(|(_, range)| **range == relative_range)?;
Some(tt::TokenId(idx as u32))
let &(token_id, _) = self.entries.iter().find(|(_, range)| *range == relative_range)?;
Some(token_id)
}
pub fn relative_range_of(&self, token_id: tt::TokenId) -> Option<TextRange> {
let idx = token_id.0 as usize;
self.tokens.get(idx).copied()
}
fn alloc(&mut self, relative_range: TextRange) -> tt::TokenId {
let id = self.tokens.len();
self.tokens.push(relative_range);
tt::TokenId(id as u32)
}
}
impl RevTokenMap {
pub fn range_by_token(&self, token_id: tt::TokenId) -> Option<TextRange> {
let &(r, _) = self.ranges.iter().find(|(_, tid)| *tid == token_id)?;
Some(r)
let &(_, range) = self.entries.iter().find(|(tid, _)| *tid == token_id)?;
Some(range)
}
fn add(&mut self, relative_range: TextRange, token_id: tt::TokenId) {
self.ranges.push((relative_range, token_id.clone()))
fn insert(&mut self, token_id: tt::TokenId, relative_range: TextRange) {
self.entries.push((token_id, relative_range));
}
}
@ -167,84 +145,98 @@ fn mk_doc_literal(comment: &ast::Comment) -> tt::TokenTree {
}
}
fn convert_tt(
token_map: &mut TokenMap,
struct Convertor {
map: TokenMap,
global_offset: TextUnit,
tt: &SyntaxNode,
) -> Option<tt::Subtree> {
// This tree is empty
if tt.first_child_or_token().is_none() {
return Some(tt::Subtree { token_trees: vec![], delimiter: tt::Delimiter::None });
}
next_id: u32,
}
let first_child = tt.first_child_or_token()?;
let last_child = tt.last_child_or_token()?;
let (delimiter, skip_first) = match (first_child.kind(), last_child.kind()) {
(T!['('], T![')']) => (tt::Delimiter::Parenthesis, true),
(T!['{'], T!['}']) => (tt::Delimiter::Brace, true),
(T!['['], T![']']) => (tt::Delimiter::Bracket, true),
_ => (tt::Delimiter::None, false),
};
let mut token_trees = Vec::new();
let mut child_iter = tt.children_with_tokens().skip(skip_first as usize).peekable();
while let Some(child) = child_iter.next() {
if skip_first && (child == first_child || child == last_child) {
continue;
impl Convertor {
fn go(&mut self, tt: &SyntaxNode) -> Option<tt::Subtree> {
// This tree is empty
if tt.first_child_or_token().is_none() {
return Some(tt::Subtree { token_trees: vec![], delimiter: tt::Delimiter::None });
}
match child {
NodeOrToken::Token(token) => {
if let Some(doc_tokens) = convert_doc_comment(&token) {
token_trees.extend(doc_tokens);
} else if token.kind().is_trivia() {
continue;
} else if token.kind().is_punct() {
assert!(token.text().len() == 1, "Input ast::token punct must be single char.");
let char = token.text().chars().next().unwrap();
let first_child = tt.first_child_or_token()?;
let last_child = tt.last_child_or_token()?;
let (delimiter, skip_first) = match (first_child.kind(), last_child.kind()) {
(T!['('], T![')']) => (tt::Delimiter::Parenthesis, true),
(T!['{'], T!['}']) => (tt::Delimiter::Brace, true),
(T!['['], T![']']) => (tt::Delimiter::Bracket, true),
_ => (tt::Delimiter::None, false),
};
let spacing = match child_iter.peek() {
Some(NodeOrToken::Token(token)) => {
if token.kind().is_punct() {
tt::Spacing::Joint
} else {
tt::Spacing::Alone
let mut token_trees = Vec::new();
let mut child_iter = tt.children_with_tokens().skip(skip_first as usize).peekable();
while let Some(child) = child_iter.next() {
if skip_first && (child == first_child || child == last_child) {
continue;
}
match child {
NodeOrToken::Token(token) => {
if let Some(doc_tokens) = convert_doc_comment(&token) {
token_trees.extend(doc_tokens);
} else if token.kind().is_trivia() {
continue;
} else if token.kind().is_punct() {
assert!(
token.text().len() == 1,
"Input ast::token punct must be single char."
);
let char = token.text().chars().next().unwrap();
let spacing = match child_iter.peek() {
Some(NodeOrToken::Token(token)) => {
if token.kind().is_punct() {
tt::Spacing::Joint
} else {
tt::Spacing::Alone
}
}
}
_ => tt::Spacing::Alone,
};
token_trees.push(tt::Leaf::from(tt::Punct { char, spacing }).into());
} else {
let child: tt::TokenTree =
if token.kind() == T![true] || token.kind() == T![false] {
tt::Leaf::from(tt::Literal { text: token.text().clone() }).into()
} else if token.kind().is_keyword()
|| token.kind() == IDENT
|| token.kind() == LIFETIME
{
let relative_range = token.text_range() - global_offset;
let id = token_map.alloc(relative_range);
let text = token.text().clone();
tt::Leaf::from(tt::Ident { text, id }).into()
} else if token.kind().is_literal() {
tt::Leaf::from(tt::Literal { text: token.text().clone() }).into()
} else {
return None;
_ => tt::Spacing::Alone,
};
token_trees.push(tt::Leaf::from(tt::Punct { char, spacing }).into());
} else {
let child: tt::TokenTree =
if token.kind() == T![true] || token.kind() == T![false] {
tt::Leaf::from(tt::Literal { text: token.text().clone() }).into()
} else if token.kind().is_keyword()
|| token.kind() == IDENT
|| token.kind() == LIFETIME
{
let id = self.alloc(token.text_range());
let text = token.text().clone();
tt::Leaf::from(tt::Ident { text, id }).into()
} else if token.kind().is_literal() {
tt::Leaf::from(tt::Literal { text: token.text().clone() }).into()
} else {
return None;
};
token_trees.push(child);
}
}
NodeOrToken::Node(node) => {
let child = self.go(&node)?.into();
token_trees.push(child);
}
}
NodeOrToken::Node(node) => {
let child = convert_tt(token_map, global_offset, &node)?.into();
token_trees.push(child);
}
};
};
}
let res = tt::Subtree { delimiter, token_trees };
Some(res)
}
let res = tt::Subtree { delimiter, token_trees };
Some(res)
fn alloc(&mut self, absolute_range: TextRange) -> tt::TokenId {
let relative_range = absolute_range - self.global_offset;
let token_id = tt::TokenId(self.next_id);
self.next_id += 1;
self.map.insert(token_id, relative_range);
token_id
}
}
struct TtTreeSink<'a> {
@ -252,7 +244,7 @@ struct TtTreeSink<'a> {
cursor: Cursor<'a>,
text_pos: TextUnit,
inner: SyntaxTreeBuilder,
range_map: RevTokenMap,
token_map: TokenMap,
// Number of roots
// Use for detect ill-form tree which is not single root
@ -267,12 +259,12 @@ fn new(cursor: Cursor<'a>) -> Self {
text_pos: 0.into(),
inner: SyntaxTreeBuilder::default(),
roots: smallvec::SmallVec::new(),
range_map: RevTokenMap::default(),
token_map: TokenMap::default(),
}
}
fn finish(self) -> (Parse<SyntaxNode>, RevTokenMap) {
(self.inner.finish(), self.range_map)
fn finish(self) -> (Parse<SyntaxNode>, TokenMap) {
(self.inner.finish(), self.token_map)
}
}
@ -308,7 +300,7 @@ fn token(&mut self, kind: SyntaxKind, n_tokens: u8) {
if kind == IDENT {
let range =
TextRange::offset_len(self.text_pos, TextUnit::of_str(&ident.text));
self.range_map.add(range, ident.id);
self.token_map.insert(ident.id, range);
}
}

View File

@ -2,7 +2,7 @@
//! Extensions for various expressions live in a sibling `expr_extensions` module.
use crate::{
ast::{self, child_opt, children, AstChildren, AstNode, AttrInput, SyntaxNode},
ast::{self, child_opt, children, AstNode, AttrInput, SyntaxNode},
SmolStr, SyntaxElement,
SyntaxKind::*,
SyntaxToken, T,
@ -176,16 +176,6 @@ pub fn is_negative(&self) -> bool {
}
}
impl ast::AttrsOwner for ast::ImplItem {
fn attrs(&self) -> AstChildren<ast::Attr> {
match self {
ast::ImplItem::FnDef(it) => it.attrs(),
ast::ImplItem::TypeAliasDef(it) => it.attrs(),
ast::ImplItem::ConstDef(it) => it.attrs(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructKind {
Tuple(ast::TupleFieldDefList),

View File

@ -1298,6 +1298,7 @@ fn syntax(&self) -> &SyntaxNode {
}
}
}
impl ast::AttrsOwner for ImplItem {}
impl ImplItem {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImplTraitType {

View File

@ -401,7 +401,8 @@ Grammar(
traits: ["AttrsOwner"]
),
"ImplItem": (
enum: ["FnDef", "TypeAliasDef", "ConstDef"]
enum: ["FnDef", "TypeAliasDef", "ConstDef"],
traits: ["AttrsOwner"]
),
"TupleExpr": (

View File

@ -55,7 +55,7 @@ We use Travis for CI. Most of the things, including formatting, are checked by
be green as well. We use bors-ng to enforce the [not rocket
science](https://graydon2.dreamwidth.org/1597.html) rule.
You can run `cargo format-hook` to install git-hook to run rustfmt on commit.
You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit.
# Code organization

View File

@ -1,31 +0,0 @@
//! FIXME: write short doc here
use std::process::Command;
use xtask::{codegen::Mode, project_root, run, run_rustfmt, Result};
fn main() -> Result<()> {
run_rustfmt(Mode::Overwrite)?;
update_staged()
}
fn update_staged() -> Result<()> {
let root = project_root();
let output = Command::new("git")
.arg("diff")
.arg("--diff-filter=MAR")
.arg("--name-only")
.arg("--cached")
.current_dir(&root)
.output()?;
if !output.status.success() {
anyhow::bail!(
"`git diff --diff-filter=MAR --name-only --cached` exited with {}",
output.status
);
}
for line in String::from_utf8(output.stdout)?.lines() {
run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
}
Ok(())
}

View File

@ -10,7 +10,7 @@
SUBCOMMANDS:
format
format-hook
install-pre-commit-hook
fuzz-tests
codegen
install

View File

@ -83,19 +83,12 @@ pub fn install_rustfmt() -> Result<()> {
run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
}
pub fn install_format_hook() -> Result<()> {
let result_path = Path::new(if cfg!(windows) {
"./.git/hooks/pre-commit.exe"
} else {
"./.git/hooks/pre-commit"
});
pub fn install_pre_commit_hook() -> Result<()> {
let result_path =
PathBuf::from(format!("./.git/hooks/pre-commit{}", std::env::consts::EXE_SUFFIX));
if !result_path.exists() {
run("cargo build --package xtask --bin pre-commit", ".")?;
if cfg!(windows) {
fs::copy("./target/debug/pre-commit.exe", result_path)?;
} else {
fs::copy("./target/debug/pre-commit", result_path)?;
}
let me = std::env::current_exe()?;
fs::copy(me, result_path)?;
} else {
Err(IoError::new(ErrorKind::AlreadyExists, "Git hook already created"))?;
}
@ -150,6 +143,27 @@ pub fn run_fuzzer() -> Result<()> {
run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
}
pub fn reformat_staged_files() -> Result<()> {
let root = project_root();
let output = Command::new("git")
.arg("diff")
.arg("--diff-filter=MAR")
.arg("--name-only")
.arg("--cached")
.current_dir(&root)
.output()?;
if !output.status.success() {
anyhow::bail!(
"`git diff --diff-filter=MAR --name-only --cached` exited with {}",
output.status
);
}
for line in String::from_utf8(output.stdout)?.lines() {
run(&format!("git update-index --add {}", root.join(line).to_string_lossy()), ".")?;
}
Ok(())
}
fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
where
F: FnMut(&mut Command),

View File

@ -16,7 +16,8 @@
use std::{env, path::PathBuf};
use xtask::{
codegen::{self, Mode},
install_format_hook, run, run_clippy, run_fuzzer, run_rustfmt, run_with_output, Cmd, Result,
install_pre_commit_hook, reformat_staged_files, run, run_clippy, run_fuzzer, run_rustfmt,
run_with_output, Cmd, Result,
};
// Latest stable, feel free to send a PR if this lags behind.
@ -36,6 +37,10 @@ struct ServerOpt {
}
fn main() -> Result<()> {
if std::env::args().next().map(|it| it.contains("pre-commit")) == Some(true) {
return reformat_staged_files();
}
let subcommand = match std::env::args_os().nth(1) {
None => {
eprintln!("{}", help::GLOBAL_HELP);
@ -81,12 +86,12 @@ fn main() -> Result<()> {
}
run_rustfmt(Mode::Overwrite)?
}
"format-hook" => {
"install-pre-commit-hook" => {
if matches.contains(["-h", "--help"]) {
help::print_no_param_subcommand_help(&subcommand);
return Ok(());
}
install_format_hook()?
install_pre_commit_hook()?
}
"lint" => {
if matches.contains(["-h", "--help"]) {