use std::{
fmt::{self, Display as _},
iter,
};
use crate::{
db::ExpandDatabase,
hygiene::{marks_rev, SyntaxContextExt, Transparency},
name::{AsName, Name},
tt,
};
use base_db::CrateId;
use intern::sym;
use smallvec::SmallVec;
use span::{Edition, SyntaxContextId};
use syntax::{ast, AstNode};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModPath {
pub kind: PathKind,
segments: SmallVec<[Name; 1]>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnescapedModPath<'a>(&'a ModPath);
impl<'a> UnescapedModPath<'a> {
pub fn display(&'a self, db: &'a dyn crate::db::ExpandDatabase) -> impl fmt::Display + 'a {
UnescapedDisplay { db, path: self }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathKind {
Plain,
Super(u8),
Crate,
Abs,
DollarCrate(CrateId),
}
impl PathKind {
pub const SELF: PathKind = PathKind::Super(0);
}
impl ModPath {
pub fn from_src(
db: &dyn ExpandDatabase,
path: ast::Path,
span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
) -> Option<ModPath> {
convert_path(db, path, span_for_range)
}
pub fn from_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option<ModPath> {
convert_path_tt(db, tt)
}
pub fn from_segments(kind: PathKind, segments: impl IntoIterator<Item = Name>) -> ModPath {
let mut segments: SmallVec<_> = segments.into_iter().collect();
segments.shrink_to_fit();
ModPath { kind, segments }
}
pub const fn from_kind(kind: PathKind) -> ModPath {
ModPath { kind, segments: SmallVec::new_const() }
}
pub fn segments(&self) -> &[Name] {
&self.segments
}
pub fn push_segment(&mut self, segment: Name) {
self.segments.push(segment);
}
pub fn pop_segment(&mut self) -> Option<Name> {
self.segments.pop()
}
pub fn len(&self) -> usize {
self.segments.len()
+ match self.kind {
PathKind::Plain => 0,
PathKind::Super(i) => i as usize,
PathKind::Crate => 1,
PathKind::Abs => 0,
PathKind::DollarCrate(_) => 1,
}
}
pub fn textual_len(&self) -> usize {
let base = match self.kind {
PathKind::Plain => 0,
PathKind::SELF => "self".len(),
PathKind::Super(i) => "super".len() * i as usize,
PathKind::Crate => "crate".len(),
PathKind::Abs => 0,
PathKind::DollarCrate(_) => "$crate".len(),
};
self.segments().iter().map(|segment| segment.as_str().len()).fold(base, core::ops::Add::add)
}
pub fn is_ident(&self) -> bool {
self.as_ident().is_some()
}
pub fn is_self(&self) -> bool {
self.kind == PathKind::SELF && self.segments.is_empty()
}
#[allow(non_snake_case)]
pub fn is_Self(&self) -> bool {
self.kind == PathKind::Plain
&& matches!(&*self.segments, [name] if *name == sym::Self_.clone())
}
pub fn as_ident(&self) -> Option<&Name> {
if self.kind != PathKind::Plain {
return None;
}
match &*self.segments {
[name] => Some(name),
_ => None,
}
}
pub fn unescaped(&self) -> UnescapedModPath<'_> {
UnescapedModPath(self)
}
pub fn display<'a>(
&'a self,
db: &'a dyn crate::db::ExpandDatabase,
edition: Edition,
) -> impl fmt::Display + 'a {
Display { db, path: self, edition }
}
}
impl Extend<Name> for ModPath {
fn extend<T: IntoIterator<Item = Name>>(&mut self, iter: T) {
self.segments.extend(iter);
}
}
struct Display<'a> {
db: &'a dyn ExpandDatabase,
path: &'a ModPath,
edition: Edition,
}
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_fmt_path(self.db, self.path, f, Escape::IfNeeded(self.edition))
}
}
struct UnescapedDisplay<'a> {
db: &'a dyn ExpandDatabase,
path: &'a UnescapedModPath<'a>,
}
impl fmt::Display for UnescapedDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
display_fmt_path(self.db, self.path.0, f, Escape::No)
}
}
impl From<Name> for ModPath {
fn from(name: Name) -> ModPath {
ModPath::from_segments(PathKind::Plain, iter::once(name))
}
}
enum Escape {
No,
IfNeeded(Edition),
}
fn display_fmt_path(
db: &dyn ExpandDatabase,
path: &ModPath,
f: &mut fmt::Formatter<'_>,
escaped: Escape,
) -> fmt::Result {
let mut first_segment = true;
let mut add_segment = |s| -> fmt::Result {
if !first_segment {
f.write_str("::")?;
}
first_segment = false;
f.write_str(s)?;
Ok(())
};
match path.kind {
PathKind::Plain => {}
PathKind::SELF => add_segment("self")?,
PathKind::Super(n) => {
for _ in 0..n {
add_segment("super")?;
}
}
PathKind::Crate => add_segment("crate")?,
PathKind::Abs => add_segment("")?,
PathKind::DollarCrate(_) => add_segment("$crate")?,
}
for segment in &path.segments {
if !first_segment {
f.write_str("::")?;
}
first_segment = false;
match escaped {
Escape::IfNeeded(edition) => segment.display(db, edition).fmt(f)?,
Escape::No => segment.unescaped().display(db).fmt(f)?,
}
}
Ok(())
}
fn convert_path(
db: &dyn ExpandDatabase,
path: ast::Path,
span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
) -> Option<ModPath> {
let mut segments = path.segments();
let segment = &segments.next()?;
let handle_super_kw = &mut |init_deg| {
let mut deg = init_deg;
let mut next_segment = None;
for segment in segments.by_ref() {
match segment.kind()? {
ast::PathSegmentKind::SuperKw => deg += 1,
ast::PathSegmentKind::Name(name) => {
next_segment = Some(name.as_name());
break;
}
ast::PathSegmentKind::Type { .. }
| ast::PathSegmentKind::SelfTypeKw
| ast::PathSegmentKind::SelfKw
| ast::PathSegmentKind::CrateKw => return None,
}
}
Some(ModPath::from_segments(PathKind::Super(deg), next_segment))
};
let mut mod_path = match segment.kind()? {
ast::PathSegmentKind::Name(name_ref) => {
if name_ref.text() == "$crate" {
ModPath::from_kind(
resolve_crate_root(db, span_for_range(name_ref.syntax().text_range()))
.map(PathKind::DollarCrate)
.unwrap_or(PathKind::Crate),
)
} else {
let mut res = ModPath::from_kind(
segment.coloncolon_token().map_or(PathKind::Plain, |_| PathKind::Abs),
);
res.segments.push(name_ref.as_name());
res
}
}
ast::PathSegmentKind::SelfTypeKw => ModPath::from_segments(
PathKind::Plain,
Some(Name::new_symbol(sym::Self_.clone(), SyntaxContextId::ROOT)),
),
ast::PathSegmentKind::CrateKw => ModPath::from_segments(PathKind::Crate, iter::empty()),
ast::PathSegmentKind::SelfKw => handle_super_kw(0)?,
ast::PathSegmentKind::SuperKw => handle_super_kw(1)?,
ast::PathSegmentKind::Type { .. } => {
return None;
}
};
for segment in segments {
let name = match segment.kind()? {
ast::PathSegmentKind::Name(name) => name.as_name(),
_ => return None,
};
mod_path.segments.push(name);
}
if mod_path.segments.len() == 1 && mod_path.kind == PathKind::Plain {
if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
let syn_ctx = span_for_range(segment.syntax().text_range());
if let Some(macro_call_id) = db.lookup_intern_syntax_context(syn_ctx).outer_expn {
if db.lookup_intern_macro_call(macro_call_id).def.local_inner {
mod_path.kind = match resolve_crate_root(db, syn_ctx) {
Some(crate_root) => PathKind::DollarCrate(crate_root),
None => PathKind::Crate,
}
}
}
}
}
Some(mod_path)
}
fn convert_path_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option<ModPath> {
let mut leaves = tt.iter().filter_map(|tt| match tt {
tt::TokenTree::Leaf(leaf) => Some(leaf),
tt::TokenTree::Subtree(_) => None,
});
let mut segments = smallvec::smallvec![];
let kind = match leaves.next()? {
tt::Leaf::Punct(tt::Punct { char: ':', .. }) => match leaves.next()? {
tt::Leaf::Punct(tt::Punct { char: ':', .. }) => PathKind::Abs,
_ => return None,
},
tt::Leaf::Ident(tt::Ident { sym: text, span, .. }) if *text == sym::dollar_crate => {
resolve_crate_root(db, span.ctx).map(PathKind::DollarCrate).unwrap_or(PathKind::Crate)
}
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::self_ => PathKind::SELF,
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::super_ => {
let mut deg = 1;
while let Some(tt::Leaf::Ident(tt::Ident { sym: text, span, is_raw: _ })) =
leaves.next()
{
if *text != sym::super_ {
segments.push(Name::new_symbol(text.clone(), span.ctx));
break;
}
deg += 1;
}
PathKind::Super(deg)
}
tt::Leaf::Ident(tt::Ident { sym: text, .. }) if *text == sym::crate_ => PathKind::Crate,
tt::Leaf::Ident(ident) => {
segments.push(Name::new_symbol(ident.sym.clone(), ident.span.ctx));
PathKind::Plain
}
_ => return None,
};
segments.extend(leaves.filter_map(|leaf| match leaf {
::tt::Leaf::Ident(ident) => Some(Name::new_symbol(ident.sym.clone(), ident.span.ctx)),
_ => None,
}));
Some(ModPath { kind, segments })
}
pub fn resolve_crate_root(db: &dyn ExpandDatabase, mut ctxt: SyntaxContextId) -> Option<CrateId> {
ctxt = ctxt.normalize_to_macro_rules(db);
let mut iter = marks_rev(ctxt, db).peekable();
let mut result_mark = None;
while let Some(&(mark, Transparency::Opaque)) = iter.peek() {
result_mark = Some(mark);
iter.next();
}
while let Some((mark, Transparency::SemiTransparent)) = iter.next() {
result_mark = Some(mark);
}
result_mark.map(|call| db.lookup_intern_macro_call(call).def.krate)
}
pub use crate::name as __name;
#[macro_export]
macro_rules! __known_path {
(core::iter::IntoIterator) => {};
(core::iter::Iterator) => {};
(core::result::Result) => {};
(core::option::Option) => {};
(core::ops::Range) => {};
(core::ops::RangeFrom) => {};
(core::ops::RangeFull) => {};
(core::ops::RangeTo) => {};
(core::ops::RangeToInclusive) => {};
(core::ops::RangeInclusive) => {};
(core::future::Future) => {};
(core::future::IntoFuture) => {};
(core::fmt::Debug) => {};
(std::fmt::format) => {};
(core::ops::Try) => {};
($path:path) => {
compile_error!("Please register your known path in the path module")
};
}
#[macro_export]
macro_rules! __path {
($start:ident $(:: $seg:ident)*) => ({
$crate::__known_path!($start $(:: $seg)*);
$crate::mod_path::ModPath::from_segments($crate::mod_path::PathKind::Abs, vec![
$crate::name::Name::new_symbol_root(intern::sym::$start.clone()), $($crate::name::Name::new_symbol_root(intern::sym::$seg.clone()),)*
])
});
}
pub use crate::__path as path;