2235: Handle macro-generated expressions slightly less wrong r=matklad a=matklad



Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-11-14 07:36:47 +00:00 committed by GitHub
commit 8af85263f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 156 additions and 100 deletions

View file

@ -40,8 +40,8 @@ pub(crate) fn body_with_source_map_query(
(src.file_id, src.ast.body())
}
};
let resolver = hir_def::body::MacroResolver::new(db, def.module(db).id);
let (body, source_map) = Body::new(db, resolver, file_id, params, body);
let expander = hir_def::body::Expander::new(db, file_id, def.module(db).id);
let (body, source_map) = Body::new(db, expander, params, body);
(Arc::new(body), Arc::new(source_map))
}

View file

@ -166,6 +166,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
#[cfg(test)]
mod tests {
use hir_expand::Source;
use ra_db::{fixture::WithFixture, SourceDatabase};
use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
use test_utils::{assert_eq_text, extract_offset};
@ -189,7 +190,10 @@ mod tests {
let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None);
let scopes = analyzer.scopes();
let expr_id = analyzer.body_source_map().node_expr(&marker.into()).unwrap();
let expr_id = analyzer
.body_source_map()
.node_expr(Source { file_id: file_id.into(), ast: &marker.into() })
.unwrap();
let scope = scopes.scope_for(expr_id);
let actual = scopes

View file

@ -145,7 +145,8 @@ impl Local {
Some(res)
})?;
let (_body, source_map) = db.body_with_source_map(parent);
let pat_id = source_map.node_pat(&src.ast.into())?;
let src = src.map(ast::Pat::from);
let pat_id = source_map.node_pat(src.as_ref())?;
Some(Local { parent, pat_id })
}
}

View file

@ -7,8 +7,11 @@
//! purely for "IDE needs".
use std::sync::Arc;
use hir_def::path::known;
use hir_expand::name::AsName;
use hir_def::{
expr::{ExprId, PatId},
path::known,
};
use hir_expand::{name::AsName, Source};
use ra_db::FileId;
use ra_syntax::{
ast::{self, AstNode},
@ -93,6 +96,8 @@ fn def_with_body_from_child_node(
/// original source files. It should not be used inside the HIR itself.
#[derive(Debug)]
pub struct SourceAnalyzer {
// FIXME: this doesn't handle macros at all
file_id: FileId,
resolver: Resolver,
body_owner: Option<DefWithBody>,
body_source_map: Option<Arc<BodySourceMap>>,
@ -147,7 +152,7 @@ impl SourceAnalyzer {
let source_map = def.body_source_map(db);
let scopes = db.expr_scopes(def);
let scope = match offset {
None => scope_for(&scopes, &source_map, &node),
None => scope_for(&scopes, &source_map, file_id.into(), &node),
Some(offset) => scope_for_offset(&scopes, &source_map, file_id.into(), offset),
};
let resolver = expr::resolver_for_scope(db, def, scope);
@ -157,6 +162,7 @@ impl SourceAnalyzer {
body_source_map: Some(source_map),
infer: Some(def.infer(db)),
scopes: Some(scopes),
file_id,
}
} else {
SourceAnalyzer {
@ -168,17 +174,28 @@ impl SourceAnalyzer {
body_source_map: None,
infer: None,
scopes: None,
file_id,
}
}
}
fn expr_id(&self, expr: &ast::Expr) -> Option<ExprId> {
let src = Source { file_id: self.file_id.into(), ast: expr };
self.body_source_map.as_ref()?.node_expr(src)
}
fn pat_id(&self, pat: &ast::Pat) -> Option<PatId> {
let src = Source { file_id: self.file_id.into(), ast: pat };
self.body_source_map.as_ref()?.node_pat(src)
}
pub fn type_of(&self, _db: &impl HirDatabase, expr: &ast::Expr) -> Option<crate::Ty> {
let expr_id = self.body_source_map.as_ref()?.node_expr(expr)?;
let expr_id = self.expr_id(expr)?;
Some(self.infer.as_ref()?[expr_id].clone())
}
pub fn type_of_pat(&self, _db: &impl HirDatabase, pat: &ast::Pat) -> Option<crate::Ty> {
let pat_id = self.body_source_map.as_ref()?.node_pat(pat)?;
let pat_id = self.pat_id(pat)?;
Some(self.infer.as_ref()?[pat_id].clone())
}
@ -191,22 +208,22 @@ impl SourceAnalyzer {
}
pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
let expr_id = self.body_source_map.as_ref()?.node_expr(&call.clone().into())?;
let expr_id = self.expr_id(&call.clone().into())?;
self.infer.as_ref()?.method_resolution(expr_id)
}
pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<crate::StructField> {
let expr_id = self.body_source_map.as_ref()?.node_expr(&field.clone().into())?;
let expr_id = self.expr_id(&field.clone().into())?;
self.infer.as_ref()?.field_resolution(expr_id)
}
pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option<crate::VariantDef> {
let expr_id = self.body_source_map.as_ref()?.node_expr(&record_lit.clone().into())?;
let expr_id = self.expr_id(&record_lit.clone().into())?;
self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
}
pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option<crate::VariantDef> {
let pat_id = self.body_source_map.as_ref()?.node_pat(&record_pat.clone().into())?;
let pat_id = self.pat_id(&record_pat.clone().into())?;
self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
}
@ -264,13 +281,13 @@ impl SourceAnalyzer {
pub fn resolve_path(&self, db: &impl HirDatabase, path: &ast::Path) -> Option<PathResolution> {
if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) {
let expr_id = self.body_source_map.as_ref()?.node_expr(&path_expr.into())?;
let expr_id = self.expr_id(&path_expr.into())?;
if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) {
return Some(PathResolution::AssocItem(assoc));
}
}
if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) {
let pat_id = self.body_source_map.as_ref()?.node_pat(&path_pat.into())?;
let pat_id = self.pat_id(&path_pat.into())?;
if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
return Some(PathResolution::AssocItem(assoc));
}
@ -285,7 +302,7 @@ impl SourceAnalyzer {
let name = name_ref.as_name();
let source_map = self.body_source_map.as_ref()?;
let scopes = self.scopes.as_ref()?;
let scope = scope_for(scopes, source_map, name_ref.syntax());
let scope = scope_for(scopes, source_map, self.file_id.into(), name_ref.syntax());
let ret = scopes
.scope_chain(scope)
.flat_map(|scope| scopes.entries(scope).iter())
@ -418,11 +435,12 @@ impl SourceAnalyzer {
fn scope_for(
scopes: &ExprScopes,
source_map: &BodySourceMap,
file_id: HirFileId,
node: &SyntaxNode,
) -> Option<ScopeId> {
node.ancestors()
.filter_map(ast::Expr::cast)
.filter_map(|it| source_map.node_expr(&it))
.filter_map(|it| source_map.node_expr(Source { file_id, ast: &it }))
.find_map(|it| scopes.scope_for(it))
}

View file

@ -3,9 +3,12 @@ mod lower;
use std::{ops::Index, sync::Arc};
use hir_expand::{either::Either, HirFileId, MacroDefId, Source};
use hir_expand::{
either::Either, hygiene::Hygiene, AstId, HirFileId, MacroCallLoc, MacroDefId, MacroFileKind,
Source,
};
use ra_arena::{map::ArenaMap, Arena};
use ra_syntax::{ast, AstPtr};
use ra_syntax::{ast, AstNode, AstPtr};
use rustc_hash::FxHashMap;
use crate::{
@ -16,25 +19,87 @@ use crate::{
ModuleId,
};
pub struct MacroResolver {
pub struct Expander {
crate_def_map: Arc<CrateDefMap>,
current_file_id: HirFileId,
hygiene: Hygiene,
module: ModuleId,
}
impl MacroResolver {
pub fn new(db: &impl DefDatabase2, module: ModuleId) -> MacroResolver {
MacroResolver { crate_def_map: db.crate_def_map(module.krate), module }
impl Expander {
pub fn new(db: &impl DefDatabase2, current_file_id: HirFileId, module: ModuleId) -> Expander {
let crate_def_map = db.crate_def_map(module.krate);
let hygiene = Hygiene::new(db, current_file_id);
Expander { crate_def_map, current_file_id, hygiene, module }
}
pub(crate) fn resolve_path_as_macro(
&self,
fn expand(
&mut self,
db: &impl DefDatabase2,
path: &Path,
) -> Option<MacroDefId> {
macro_call: ast::MacroCall,
) -> Option<(Mark, ast::Expr)> {
let ast_id = AstId::new(
self.current_file_id,
db.ast_id_map(self.current_file_id).ast_id(&macro_call),
);
if let Some(path) = macro_call.path().and_then(|path| self.parse_path(path)) {
if let Some(def) = self.resolve_path_as_macro(db, &path) {
let call_id = db.intern_macro(MacroCallLoc { def, ast_id });
let file_id = call_id.as_file(MacroFileKind::Expr);
if let Some(node) = db.parse_or_expand(file_id) {
if let Some(expr) = ast::Expr::cast(node) {
log::debug!("macro expansion {:#?}", expr.syntax());
let mark = self.enter(db, file_id);
return Some((mark, expr));
}
}
}
}
// FIXME: Instead of just dropping the error from expansion
// report it
None
}
fn enter(&mut self, db: &impl DefDatabase2, file_id: HirFileId) -> Mark {
let mark = Mark { file_id: self.current_file_id };
self.hygiene = Hygiene::new(db, file_id);
self.current_file_id = file_id;
mark
}
fn exit(&mut self, db: &impl DefDatabase2, mark: Mark) {
self.hygiene = Hygiene::new(db, mark.file_id);
self.current_file_id = mark.file_id;
std::mem::forget(mark);
}
fn to_source<T>(&self, ast: T) -> Source<T> {
Source { file_id: self.current_file_id, ast }
}
fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
Path::from_src(path, &self.hygiene)
}
fn resolve_path_as_macro(&self, db: &impl DefDatabase2, path: &Path) -> Option<MacroDefId> {
self.crate_def_map.resolve_path(db, self.module.module_id, path).0.get_macros()
}
}
struct Mark {
file_id: HirFileId,
}
impl Drop for Mark {
fn drop(&mut self) {
if !std::thread::panicking() {
panic!("dropped mark")
}
}
}
/// The body of an item (function, const etc.).
#[derive(Debug, Eq, PartialEq)]
pub struct Body {
@ -70,9 +135,9 @@ pub type PatSource = Source<PatPtr>;
/// this properly for macros.
#[derive(Default, Debug, Eq, PartialEq)]
pub struct BodySourceMap {
expr_map: FxHashMap<ExprPtr, ExprId>,
expr_map: FxHashMap<ExprSource, ExprId>,
expr_map_back: ArenaMap<ExprId, ExprSource>,
pat_map: FxHashMap<PatPtr, PatId>,
pat_map: FxHashMap<PatSource, PatId>,
pat_map_back: ArenaMap<PatId, PatSource>,
field_map: FxHashMap<(ExprId, usize), AstPtr<ast::RecordField>>,
}
@ -80,12 +145,11 @@ pub struct BodySourceMap {
impl Body {
pub fn new(
db: &impl DefDatabase2,
resolver: MacroResolver,
file_id: HirFileId,
expander: Expander,
params: Option<ast::ParamList>,
body: Option<ast::Expr>,
) -> (Body, BodySourceMap) {
lower::lower(db, resolver, file_id, params, body)
lower::lower(db, expander, params, body)
}
pub fn params(&self) -> &[PatId] {
@ -126,16 +190,18 @@ impl BodySourceMap {
self.expr_map_back.get(expr).copied()
}
pub fn node_expr(&self, node: &ast::Expr) -> Option<ExprId> {
self.expr_map.get(&Either::A(AstPtr::new(node))).cloned()
pub fn node_expr(&self, node: Source<&ast::Expr>) -> Option<ExprId> {
let src = node.map(|it| Either::A(AstPtr::new(it)));
self.expr_map.get(&src).cloned()
}
pub fn pat_syntax(&self, pat: PatId) -> Option<PatSource> {
self.pat_map_back.get(pat).copied()
}
pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
self.pat_map.get(&Either::A(AstPtr::new(node))).cloned()
pub fn node_pat(&self, node: Source<&ast::Pat>) -> Option<PatId> {
let src = node.map(|it| Either::A(AstPtr::new(it)));
self.pat_map.get(&src).cloned()
}
pub fn field_syntax(&self, expr: ExprId, field: usize) -> AstPtr<ast::RecordField> {

View file

@ -2,9 +2,7 @@
use hir_expand::{
either::Either,
hygiene::Hygiene,
name::{self, AsName, Name},
AstId, HirFileId, MacroCallLoc, MacroFileKind, Source,
};
use ra_arena::Arena;
use ra_syntax::{
@ -16,7 +14,7 @@ use ra_syntax::{
};
use crate::{
body::{Body, BodySourceMap, MacroResolver, PatPtr},
body::{Body, BodySourceMap, Expander, PatPtr},
builtin_type::{BuiltinFloat, BuiltinInt},
db::DefDatabase2,
expr::{
@ -30,16 +28,13 @@ use crate::{
pub(super) fn lower(
db: &impl DefDatabase2,
resolver: MacroResolver,
file_id: HirFileId,
expander: Expander,
params: Option<ast::ParamList>,
body: Option<ast::Expr>,
) -> (Body, BodySourceMap) {
ExprCollector {
resolver,
expander,
db,
original_file_id: file_id,
current_file_id: file_id,
source_map: BodySourceMap::default(),
body: Body {
exprs: Arena::default(),
@ -53,9 +48,7 @@ pub(super) fn lower(
struct ExprCollector<DB> {
db: DB,
resolver: MacroResolver,
original_file_id: HirFileId,
current_file_id: HirFileId,
expander: Expander,
body: Body,
source_map: BodySourceMap,
@ -101,12 +94,9 @@ where
fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr<ast::Expr>) -> ExprId {
let ptr = Either::A(ptr);
let id = self.body.exprs.alloc(expr);
if self.current_file_id == self.original_file_id {
self.source_map.expr_map.insert(ptr, id);
}
self.source_map
.expr_map_back
.insert(id, Source { file_id: self.current_file_id, ast: ptr });
let src = self.expander.to_source(ptr);
self.source_map.expr_map.insert(src, id);
self.source_map.expr_map_back.insert(id, src);
id
}
// desugared exprs don't have ptr, that's wrong and should be fixed
@ -117,20 +107,16 @@ where
fn alloc_expr_field_shorthand(&mut self, expr: Expr, ptr: AstPtr<ast::RecordField>) -> ExprId {
let ptr = Either::B(ptr);
let id = self.body.exprs.alloc(expr);
if self.current_file_id == self.original_file_id {
self.source_map.expr_map.insert(ptr, id);
}
self.source_map
.expr_map_back
.insert(id, Source { file_id: self.current_file_id, ast: ptr });
let src = self.expander.to_source(ptr);
self.source_map.expr_map.insert(src, id);
self.source_map.expr_map_back.insert(id, src);
id
}
fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
let id = self.body.pats.alloc(pat);
if self.current_file_id == self.original_file_id {
self.source_map.pat_map.insert(ptr, id);
}
self.source_map.pat_map_back.insert(id, Source { file_id: self.current_file_id, ast: ptr });
let src = self.expander.to_source(ptr);
self.source_map.pat_map.insert(src, id);
self.source_map.pat_map_back.insert(id, src);
id
}
@ -272,7 +258,7 @@ where
ast::Expr::PathExpr(e) => {
let path = e
.path()
.and_then(|path| self.parse_path(path))
.and_then(|path| self.expander.parse_path(path))
.map(Expr::Path)
.unwrap_or(Expr::Missing);
self.alloc_expr(path, syntax_ptr)
@ -288,7 +274,8 @@ where
ast::Expr::ParenExpr(e) => {
let inner = self.collect_expr_opt(e.expr());
// make the paren expr point to the inner expression as well
self.source_map.expr_map.insert(Either::A(syntax_ptr), inner);
let src = self.expander.to_source(Either::A(syntax_ptr));
self.source_map.expr_map.insert(src, inner);
inner
}
ast::Expr::ReturnExpr(e) => {
@ -296,7 +283,7 @@ where
self.alloc_expr(Expr::Return { expr }, syntax_ptr)
}
ast::Expr::RecordLit(e) => {
let path = e.path().and_then(|path| self.parse_path(path));
let path = e.path().and_then(|path| self.expander.parse_path(path));
let mut field_ptrs = Vec::new();
let record_lit = if let Some(nfl) = e.record_field_list() {
let fields = nfl
@ -443,32 +430,14 @@ where
// FIXME implement HIR for these:
ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
ast::Expr::RangeExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr),
ast::Expr::MacroCall(e) => {
let ast_id = AstId::new(
self.current_file_id,
self.db.ast_id_map(self.current_file_id).ast_id(&e),
);
if let Some(path) = e.path().and_then(|path| self.parse_path(path)) {
if let Some(def) = self.resolver.resolve_path_as_macro(self.db, &path) {
let call_id = self.db.intern_macro(MacroCallLoc { def, ast_id });
let file_id = call_id.as_file(MacroFileKind::Expr);
if let Some(node) = self.db.parse_or_expand(file_id) {
if let Some(expr) = ast::Expr::cast(node) {
log::debug!("macro expansion {:#?}", expr.syntax());
let old_file_id =
std::mem::replace(&mut self.current_file_id, file_id);
let id = self.collect_expr(expr);
self.current_file_id = old_file_id;
return id;
}
}
}
ast::Expr::MacroCall(e) => match self.expander.expand(self.db, e) {
Some((mark, expansion)) => {
let id = self.collect_expr(expansion);
self.expander.exit(self.db, mark);
id
}
// FIXME: Instead of just dropping the error from expansion
// report it
self.alloc_expr(Expr::Missing, syntax_ptr)
}
None => self.alloc_expr(Expr::Missing, syntax_ptr),
},
}
}
@ -519,7 +488,7 @@ where
Pat::Bind { name, mode: annotation, subpat }
}
ast::Pat::TupleStructPat(p) => {
let path = p.path().and_then(|path| self.parse_path(path));
let path = p.path().and_then(|path| self.expander.parse_path(path));
let args = p.args().map(|p| self.collect_pat(p)).collect();
Pat::TupleStruct { path, args }
}
@ -529,7 +498,7 @@ where
Pat::Ref { pat, mutability }
}
ast::Pat::PathPat(p) => {
let path = p.path().and_then(|path| self.parse_path(path));
let path = p.path().and_then(|path| self.expander.parse_path(path));
path.map(Pat::Path).unwrap_or(Pat::Missing)
}
ast::Pat::TuplePat(p) => {
@ -538,7 +507,7 @@ where
}
ast::Pat::PlaceholderPat(_) => Pat::Wild,
ast::Pat::RecordPat(p) => {
let path = p.path().and_then(|path| self.parse_path(path));
let path = p.path().and_then(|path| self.expander.parse_path(path));
let record_field_pat_list =
p.record_field_pat_list().expect("every struct should have a field list");
let mut fields: Vec<_> = record_field_pat_list
@ -579,11 +548,6 @@ where
self.missing_pat()
}
}
fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
let hygiene = Hygiene::new(self.db, self.current_file_id);
Path::from_src(path, &hygiene)
}
}
impl From<ast::BinOp> for BinaryOp {

View file

@ -223,7 +223,7 @@ impl<N: AstNode> AstId<N> {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Source<T> {
pub file_id: HirFileId,
pub ast: T,
@ -233,6 +233,9 @@ impl<T> Source<T> {
pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
Source { file_id: self.file_id, ast: f(self.ast) }
}
pub fn as_ref(&self) -> Source<&T> {
Source { file_id: self.file_id, ast: &self.ast }
}
pub fn file_syntax(&self, db: &impl db::AstDatabase) -> SyntaxNode {
db.parse_or_expand(self.file_id).expect("source created from invalid file")
}