<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// FIXME
#![warn(warnings)]

pub use self::interface::{QuirksMode, Quirks, LimitedQuirks, NoQuirks};
pub use self::interface::TreeSink;

use tokenizer;
use tokenizer::{Doctype, Attribute, AttrName, TagKind, StartTag, EndTag, Tag};
use tokenizer::TokenSink;
use tokenizer::states::{RawData, RawKind, Rcdata, Rawtext, ScriptData};

use util::atom::Atom;
use util::namespace::HTML;
use util::str::{is_ascii_whitespace, Runs};

use std::default::Default;
use std::mem::replace;

mod interface;
mod states;
mod data;

/// We mostly only work with these tokens. Everything else is handled
/// specially at the beginning of `process_in_mode`.
#[deriving(Eq, TotalEq, Clone, Show)]
enum Token {
    TagToken(Tag),
    CommentToken(StrBuf),
    CharacterTokens(bool, StrBuf),
    EOFToken,
}

/// Tree builder options, with an impl for Default.
#[deriving(Clone)]
pub struct TreeBuilderOpts {
    /// Is scripting enabled?
    pub scripting_enabled: bool,

    /// Is this an iframe srcdoc document?
    pub iframe_srcdoc: bool,

    /// Are we parsing a HTML fragment?
    pub fragment: bool,
}

impl Default for TreeBuilderOpts {
    fn default() -> TreeBuilderOpts {
        TreeBuilderOpts {
            scripting_enabled: true,
            iframe_srcdoc: false,
            fragment: false,
        }
    }
}

pub struct TreeBuilder<'sink, Handle, Sink> {
    /// Options controlling the behavior of the tree builder.
    opts: TreeBuilderOpts,

    /// Consumer of tree modifications.
    sink: &'sink mut Sink,

    /// Insertion mode.
    mode: states::InsertionMode,

    /// Original insertion mode, used by Text and InTableText modes.
    orig_mode: Option<states::InsertionMode>,

    /// The document node, which is created by the sink.
    doc_handle: Handle,

    /// Stack of open elements, most recently added at end.
    open_elems: Vec<Handle>,

    /// Head element pointer.
    head_elem: Option<Handle>,

    /// Next state change for the tokenizer, if any.
    next_tokenizer_state: Option<tokenizer::states::State>,
}

// We use guards, so we can't bind tags by move.  Instead, bind by ref
// mut and take attrs with `replace`.  This is basically fine since
// empty `Vec` doesn't allocate.
fn take_attrs(t: &mut Tag) -> Vec<Attribute> {
    replace(&mut t.attrs, vec!())
}

enum ProcessResult {
    Done,
    Reprocess(states::InsertionMode, Token),
}

macro_rules! tag_pattern (
    ($kind:ident     $var:ident) => ( TagToken(ref     $var @ Tag { kind: $kind, ..}) );
    ($kind:ident mut $var:ident) => ( TagToken(ref mut $var @ Tag { kind: $kind, ..}) );
)

macro_rules! start ( ($($args:tt)*) => ( tag_pattern!(StartTag $($args)*) ))
macro_rules! end   ( ($($args:tt)*) => ( tag_pattern!(EndTag   $($args)*) ))

macro_rules! named ( ($t:expr, $($atom:ident)*) => (
    match_atom!($t.name { $($atom)* => true, _ => false })
))

macro_rules! kind_named ( ($kind:ident $t:expr, $($atom:ident)*) => (
    match $t {
        tag_pattern!($kind t) => named!(t, $($atom)*),
        _ => false,
    }
))

macro_rules! start_named ( ($($args:tt)*) => ( kind_named!(StartTag $($args)*) ))
macro_rules! end_named   ( ($($args:tt)*) => ( kind_named!(EndTag   $($args)*) ))

impl<'sink, Handle: Clone, Sink: TreeSink<Handle>> TreeBuilder<'sink, Handle, Sink> {
    pub fn new(sink: &'sink mut Sink, opts: TreeBuilderOpts) -> TreeBuilder<'sink, Handle, Sink> {
        let doc_handle = sink.get_document();
        TreeBuilder {
            opts: opts,
            sink: sink,
            mode: states::Initial,
            orig_mode: None,
            doc_handle: doc_handle,
            open_elems: vec!(),
            head_elem: None,
            next_tokenizer_state: None,
        }
    }

    // Switch to `Text` insertion mode, save the old mode, and
    // switch the tokenizer to a raw-data state.
    // The latter only takes effect after the current / next
    // `process_token` of a start tag returns!
    fn parse_raw_data(&mut self, k: RawKind) {
        assert!(self.next_tokenizer_state.is_none());
        self.next_tokenizer_state = Some(RawData(k));
        self.orig_mode = Some(self.mode);
        self.mode = states::Text;
    }

    // The "appropriate place for inserting a node".
    fn target(&self) -> Handle {
        // FIXME: foster parenting, templates, other nonsense
        self.open_elems.last().expect("no current element").clone()
    }

    fn push(&mut self, elem: &Handle) {
        self.open_elems.push(elem.clone());
    }

    fn pop(&mut self) -> Handle {
        self.open_elems.pop().expect("no current element")
    }

    fn create_root(&mut self, attrs: Vec<Attribute>) {
        let elem = self.sink.create_element(HTML, atom!(html), attrs);
        self.push(&elem);
        self.sink.append_element(self.doc_handle.clone(), elem);
        // FIXME: application cache selection algorithm
    }

    fn create_element_impl(&mut self, push: bool, name: Atom, attrs: Vec<Attribute>) -> Handle {
        let target = self.target();
        let elem = self.sink.create_element(HTML, name, attrs);
        if push {
            self.push(&elem);
        }
        self.sink.append_element(target, elem.clone());
        // FIXME: Remove from the stack if we can't append?
        elem
    }

    fn create_element(&mut self, name: Atom, attrs: Vec<Attribute>) -> Handle {
        self.create_element_impl(true, name, attrs)
    }

    fn create_element_nopush(&mut self, name: Atom, attrs: Vec<Attribute>) -> Handle {
        self.create_element_impl(false, name, attrs)
    }

    fn create_element_for(&mut self, tag: &mut Tag) -> Handle {
        self.create_element(tag.name.clone(), take_attrs(tag))
    }

    fn process_in_mode(&mut self, mut mode: states::InsertionMode, token: tokenizer::Token) {
        // Handle `ParseError` and `DoctypeToken`; convert everything else to the local `Token` type.
        let token = match token {
            tokenizer::ParseError(e) => {
                self.sink.parse_error(e);
                return;
            }

            tokenizer::DoctypeToken(dt) => if mode == states::Initial {
                let (err, quirk) = data::doctype_error_and_quirks(&dt, self.opts.iframe_srcdoc);
                if err {
                    self.sink.parse_error(format!("Bad DOCTYPE: {}", dt));
                }
                let Doctype { name, public_id, system_id, force_quirks: _ } = dt;
                self.sink.append_doctype_to_document(
                    name.unwrap_or(StrBuf::new()),
                    public_id.unwrap_or(StrBuf::new()),
                    system_id.unwrap_or(StrBuf::new())
                );
                self.sink.set_quirks_mode(quirk);

                self.mode = states::BeforeHtml;
                return;
            } else {
                self.sink.parse_error(format!("DOCTYPE in insertion mode {:?}", mode));
                return;
            },

            tokenizer::TagToken(x) => TagToken(x),
            tokenizer::CommentToken(x) => CommentToken(x),
            tokenizer::CharacterTokens(x) => CharacterTokens(false, x),
            tokenizer::EOFToken => EOFToken,
        };

        // Do we split the token into whitespace / non-whitespace and, if so,
        // do we keep the whitespace or just drop it?
        let (process_whitespace, keep_whitespace) = match mode {
            states::InHead | states::InHeadNoscript
                => (true, true),
            states::Initial | states::BeforeHtml | states::BeforeHead
                => (true, false),
            _ => (false, false)
        };

        match (process_whitespace, token) {
            (true, CharacterTokens(_, buf)) => self.process_after_whitespace(mode,
                Runs::new(is_ascii_whitespace, buf.as_slice())
                    .filter(|&(m, _)| keep_whitespace || !m)
                    .map(|(m, b)| CharacterTokens(m, b.to_strbuf()))),

            (_, token) => self.process_after_whitespace(mode, Some(token).move_iter()),
        }
    }

    fn process_after_whitespace<Iter: Iterator<Token>>
        (&mut self,
         mut mode: states::InsertionMode,
         mut tokens: Iter) {

        for mut token in tokens {
            loop {
                match self.process_local(mode, token) {
                    Done => break,
                    Reprocess(m, t) => {
                        mode = m;
                        token = t;
                    }
                }
            }
        }
    }

    fn process_local(&mut self, mode: states::InsertionMode, mut token: Token) -> ProcessResult {
        debug!("processing {} in insertion mode {:?}", token, mode);

        match mode {
            states::Initial => match token {
                CommentToken(text) => {
                    self.sink.append_comment(self.doc_handle.clone(), text);
                    Done
                }
                token => {
                    if !self.opts.iframe_srcdoc {
                        self.sink.parse_error(format!("Bad token in Initial insertion mode: {}", token));
                        self.sink.set_quirks_mode(Quirks);
                    }
                    Reprocess(states::BeforeHtml, token)
                }
            },

            states::BeforeHtml => match token {
                CommentToken(text) => {
                    self.sink.append_comment(self.doc_handle.clone(), text);
                    Done
                }
                start!(mut t) if named!(t, html) => {
                    self.create_root(take_attrs(t));
                    Done
                }
                end!(t) if !named!(t, head body html br) => {
                    self.sink.parse_error(format!("Unexpected end tag in BeforeHtml mode: {}", t));
                    Done
                }
                token => {
                    self.create_root(vec!());
                    Reprocess(states::BeforeHead, token)
                }
            },

            states::BeforeHead => match token {
                CommentToken(text) => {
                    let target = self.target();
                    self.sink.append_comment(target, text);
                    Done
                }
                end!(t) if !named!(t, head body html br) => {
                    self.sink.parse_error(format!("Unexpected end tag in BeforeHead mode: {}", t));
                    Done
                }
                start!(mut t) if named!(t, head) => {
                    self.head_elem = Some(self.create_element_for(t));
                    self.mode = states::InHead;
                    Done
                }
                token => if start_named!(token, html) {
                    // Do this here because we can't move out of `token` when it's borrowed.
                    self.process_local(states::InBody, token)
                } else {
                    self.head_elem = Some(self.create_element(atom!(head), vec!()));
                    Reprocess(states::InHead, token)
                },
            },

            states::InHead => match token {
                CharacterTokens(true, text) => {
                    let target = self.target();
                    self.sink.append_text(target, text);
                    Done
                }
                CommentToken(text) => {
                    let target = self.target();
                    self.sink.append_comment(target, text);
                    Done
                }
                start!(mut t) if match_atom!(t.name {
                    base basefont bgsound link meta => {
                        self.create_element_nopush(t.name.clone(), take_attrs(t));
                        /* FIXME: handle charset= and http-equiv="Content-Type"
                        if named!(t, meta) {
                            ...
                        }
                        */
                        true
                    }
                    title => {
                        self.parse_raw_data(Rcdata);
                        self.create_element_for(t);
                        true
                    }
                    noframes style noscript => {
                        if (!self.opts.scripting_enabled) && named!(t, noscript) {
                            self.create_element_for(t);
                            self.mode = states::InHeadNoscript;
                        } else {
                            self.parse_raw_data(Rawtext);
                            self.create_element_for(t);
                        }
                        true
                    }
                    script => {
                        let target = self.target();
                        let elem = self.sink.create_element(HTML, atom!(script), take_attrs(t));
                        if self.opts.fragment {
                            self.sink.mark_script_already_started(elem.clone());
                        }
                        self.push(&elem);
                        self.sink.append_element(target, elem);
                        self.parse_raw_data(ScriptData);
                        true
                    }
                    template => fail!("FIXME: <template> not implemented"),
                    head => {
                        self.sink.parse_error("<head> in insertion mode InHead".to_owned());
                        true
                    }
                    _ => false,
                }) => Done,
                end!(mut t) if match_atom!(t.name {
                    head => {
                        self.pop();
                        self.mode = states::AfterHead;
                        true
                    }
                    body html br => false,
                    template => fail!("FIXME: <template> not implemented"),
                    _ => {
                        self.sink.parse_error(format!("Unexpected end tag in InHead mode: {}", t));
                        true
                    }
                }) => Done,
                token => if start_named!(token, html) {
                    // Do this here because we can't move out of `token` when it's borrowed.
                    self.process_local(states::InBody, token)
                } else {
                    self.pop();
                    Reprocess(states::AfterHead, token)
                },
            },

            states::InHeadNoscript => match token {
                CharacterTokens(true, text) => {
                    let target = self.target();
                    self.sink.append_text(target, text);
                    Done
                }
                end!(t) if match_atom!(t.name {
                    noscript => {
                        self.pop();
                        self.mode = states::InHead;
                        true
                    }
                    br => false,
                    _ => {
                        self.sink.parse_error(format!("Unexpected end tag in InHeadNoscript mode: {}", t));
                        true
                    }
                }) => Done,

                start!(t) if named!(t, head noscript) => {
                    self.sink.parse_error(format!("Unexpected start tag in InHeadNoscript mode: {}", t));
                    Done
                }

                token @ CommentToken(_) => self.process_local(states::InHead, token),

                token => if start_named!(token, html) {
                    self.process_local(states::InBody, token)
                } else if start_named!(token, basefont bgsound link meta noframes style) {
                    self.process_local(states::InHead, token)
                } else {
                    self.sink.parse_error(format!("Unexpected token in InHeadNoscript mode: {}", token));
                    self.pop();
                    Reprocess(states::InHead, token)
                },
            },

              states::AfterHead
            | states::InBody
            | states::Text
            | states::InTable
            | states::InTableText
            | states::InCaption
            | states::InColumnGroup
            | states::InTableBody
            | states::InRow
            | states::InCell
            | states::InSelect
            | states::InSelectInTable
            | states::InTemplate
            | states::AfterBody
            | states::InFrameset
            | states::AfterFrameset
            | states::AfterAfterBody
            | states::AfterAfterFrameset
                => fail!("not implemented"),
        }
    }
}

impl<'sink, Handle: Clone, Sink: TreeSink<Handle>> TokenSink for TreeBuilder<'sink, Handle, Sink> {
    fn process_token(&mut self, token: tokenizer::Token) {
        self.process_in_mode(self.mode, token);
    }

    fn query_state_change(&mut self) -> Option<tokenizer::states::State> {
        self.next_tokenizer_state.take()
    }
}
<commit_msg>Move and rename tree builder token preprocessing<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// FIXME
#![warn(warnings)]

pub use self::interface::{QuirksMode, Quirks, LimitedQuirks, NoQuirks};
pub use self::interface::TreeSink;

use tokenizer;
use tokenizer::{Doctype, Attribute, AttrName, TagKind, StartTag, EndTag, Tag};
use tokenizer::TokenSink;
use tokenizer::states::{RawData, RawKind, Rcdata, Rawtext, ScriptData};

use util::atom::Atom;
use util::namespace::HTML;
use util::str::{is_ascii_whitespace, Runs};

use std::default::Default;
use std::mem::replace;

mod interface;
mod states;
mod data;

/// We mostly only work with these tokens. Everything else is handled
/// specially at the beginning of `process_token`.
#[deriving(Eq, TotalEq, Clone, Show)]
enum Token {
    TagToken(Tag),
    CommentToken(StrBuf),
    CharacterTokens(bool, StrBuf),
    EOFToken,
}

/// Tree builder options, with an impl for Default.
#[deriving(Clone)]
pub struct TreeBuilderOpts {
    /// Is scripting enabled?
    pub scripting_enabled: bool,

    /// Is this an iframe srcdoc document?
    pub iframe_srcdoc: bool,

    /// Are we parsing a HTML fragment?
    pub fragment: bool,
}

impl Default for TreeBuilderOpts {
    fn default() -> TreeBuilderOpts {
        TreeBuilderOpts {
            scripting_enabled: true,
            iframe_srcdoc: false,
            fragment: false,
        }
    }
}

pub struct TreeBuilder<'sink, Handle, Sink> {
    /// Options controlling the behavior of the tree builder.
    opts: TreeBuilderOpts,

    /// Consumer of tree modifications.
    sink: &'sink mut Sink,

    /// Insertion mode.
    mode: states::InsertionMode,

    /// Original insertion mode, used by Text and InTableText modes.
    orig_mode: Option<states::InsertionMode>,

    /// The document node, which is created by the sink.
    doc_handle: Handle,

    /// Stack of open elements, most recently added at end.
    open_elems: Vec<Handle>,

    /// Head element pointer.
    head_elem: Option<Handle>,

    /// Next state change for the tokenizer, if any.
    next_tokenizer_state: Option<tokenizer::states::State>,
}

// We use guards, so we can't bind tags by move.  Instead, bind by ref
// mut and take attrs with `replace`.  This is basically fine since
// empty `Vec` doesn't allocate.
fn take_attrs(t: &mut Tag) -> Vec<Attribute> {
    replace(&mut t.attrs, vec!())
}

enum ProcessResult {
    Done,
    Reprocess(states::InsertionMode, Token),
}

macro_rules! tag_pattern (
    ($kind:ident     $var:ident) => ( TagToken(ref     $var @ Tag { kind: $kind, ..}) );
    ($kind:ident mut $var:ident) => ( TagToken(ref mut $var @ Tag { kind: $kind, ..}) );
)

macro_rules! start ( ($($args:tt)*) => ( tag_pattern!(StartTag $($args)*) ))
macro_rules! end   ( ($($args:tt)*) => ( tag_pattern!(EndTag   $($args)*) ))

macro_rules! named ( ($t:expr, $($atom:ident)*) => (
    match_atom!($t.name { $($atom)* => true, _ => false })
))

macro_rules! kind_named ( ($kind:ident $t:expr, $($atom:ident)*) => (
    match $t {
        tag_pattern!($kind t) => named!(t, $($atom)*),
        _ => false,
    }
))

macro_rules! start_named ( ($($args:tt)*) => ( kind_named!(StartTag $($args)*) ))
macro_rules! end_named   ( ($($args:tt)*) => ( kind_named!(EndTag   $($args)*) ))

impl<'sink, Handle: Clone, Sink: TreeSink<Handle>> TreeBuilder<'sink, Handle, Sink> {
    pub fn new(sink: &'sink mut Sink, opts: TreeBuilderOpts) -> TreeBuilder<'sink, Handle, Sink> {
        let doc_handle = sink.get_document();
        TreeBuilder {
            opts: opts,
            sink: sink,
            mode: states::Initial,
            orig_mode: None,
            doc_handle: doc_handle,
            open_elems: vec!(),
            head_elem: None,
            next_tokenizer_state: None,
        }
    }

    // Switch to `Text` insertion mode, save the old mode, and
    // switch the tokenizer to a raw-data state.
    // The latter only takes effect after the current / next
    // `process_token` of a start tag returns!
    fn parse_raw_data(&mut self, k: RawKind) {
        assert!(self.next_tokenizer_state.is_none());
        self.next_tokenizer_state = Some(RawData(k));
        self.orig_mode = Some(self.mode);
        self.mode = states::Text;
    }

    // The "appropriate place for inserting a node".
    fn target(&self) -> Handle {
        // FIXME: foster parenting, templates, other nonsense
        self.open_elems.last().expect("no current element").clone()
    }

    fn push(&mut self, elem: &Handle) {
        self.open_elems.push(elem.clone());
    }

    fn pop(&mut self) -> Handle {
        self.open_elems.pop().expect("no current element")
    }

    fn create_root(&mut self, attrs: Vec<Attribute>) {
        let elem = self.sink.create_element(HTML, atom!(html), attrs);
        self.push(&elem);
        self.sink.append_element(self.doc_handle.clone(), elem);
        // FIXME: application cache selection algorithm
    }

    fn create_element_impl(&mut self, push: bool, name: Atom, attrs: Vec<Attribute>) -> Handle {
        let target = self.target();
        let elem = self.sink.create_element(HTML, name, attrs);
        if push {
            self.push(&elem);
        }
        self.sink.append_element(target, elem.clone());
        // FIXME: Remove from the stack if we can't append?
        elem
    }

    fn create_element(&mut self, name: Atom, attrs: Vec<Attribute>) -> Handle {
        self.create_element_impl(true, name, attrs)
    }

    fn create_element_nopush(&mut self, name: Atom, attrs: Vec<Attribute>) -> Handle {
        self.create_element_impl(false, name, attrs)
    }

    fn create_element_for(&mut self, tag: &mut Tag) -> Handle {
        self.create_element(tag.name.clone(), take_attrs(tag))
    }

    fn process_local_tokens<Iter: Iterator<Token>>
        (&mut self,
         mut mode: states::InsertionMode,
         mut tokens: Iter) {

        for mut token in tokens {
            loop {
                match self.process_local(mode, token) {
                    Done => break,
                    Reprocess(m, t) => {
                        mode = m;
                        token = t;
                    }
                }
            }
        }
    }

    fn process_local(&mut self, mode: states::InsertionMode, mut token: Token) -> ProcessResult {
        debug!("processing {} in insertion mode {:?}", token, mode);

        match mode {
            states::Initial => match token {
                CommentToken(text) => {
                    self.sink.append_comment(self.doc_handle.clone(), text);
                    Done
                }
                token => {
                    if !self.opts.iframe_srcdoc {
                        self.sink.parse_error(format!("Bad token in Initial insertion mode: {}", token));
                        self.sink.set_quirks_mode(Quirks);
                    }
                    Reprocess(states::BeforeHtml, token)
                }
            },

            states::BeforeHtml => match token {
                CommentToken(text) => {
                    self.sink.append_comment(self.doc_handle.clone(), text);
                    Done
                }
                start!(mut t) if named!(t, html) => {
                    self.create_root(take_attrs(t));
                    Done
                }
                end!(t) if !named!(t, head body html br) => {
                    self.sink.parse_error(format!("Unexpected end tag in BeforeHtml mode: {}", t));
                    Done
                }
                token => {
                    self.create_root(vec!());
                    Reprocess(states::BeforeHead, token)
                }
            },

            states::BeforeHead => match token {
                CommentToken(text) => {
                    let target = self.target();
                    self.sink.append_comment(target, text);
                    Done
                }
                end!(t) if !named!(t, head body html br) => {
                    self.sink.parse_error(format!("Unexpected end tag in BeforeHead mode: {}", t));
                    Done
                }
                start!(mut t) if named!(t, head) => {
                    self.head_elem = Some(self.create_element_for(t));
                    self.mode = states::InHead;
                    Done
                }
                token => if start_named!(token, html) {
                    // Do this here because we can't move out of `token` when it's borrowed.
                    self.process_local(states::InBody, token)
                } else {
                    self.head_elem = Some(self.create_element(atom!(head), vec!()));
                    Reprocess(states::InHead, token)
                },
            },

            states::InHead => match token {
                CharacterTokens(true, text) => {
                    let target = self.target();
                    self.sink.append_text(target, text);
                    Done
                }
                CommentToken(text) => {
                    let target = self.target();
                    self.sink.append_comment(target, text);
                    Done
                }
                start!(mut t) if match_atom!(t.name {
                    base basefont bgsound link meta => {
                        self.create_element_nopush(t.name.clone(), take_attrs(t));
                        /* FIXME: handle charset= and http-equiv="Content-Type"
                        if named!(t, meta) {
                            ...
                        }
                        */
                        true
                    }
                    title => {
                        self.parse_raw_data(Rcdata);
                        self.create_element_for(t);
                        true
                    }
                    noframes style noscript => {
                        if (!self.opts.scripting_enabled) && named!(t, noscript) {
                            self.create_element_for(t);
                            self.mode = states::InHeadNoscript;
                        } else {
                            self.parse_raw_data(Rawtext);
                            self.create_element_for(t);
                        }
                        true
                    }
                    script => {
                        let target = self.target();
                        let elem = self.sink.create_element(HTML, atom!(script), take_attrs(t));
                        if self.opts.fragment {
                            self.sink.mark_script_already_started(elem.clone());
                        }
                        self.push(&elem);
                        self.sink.append_element(target, elem);
                        self.parse_raw_data(ScriptData);
                        true
                    }
                    template => fail!("FIXME: <template> not implemented"),
                    head => {
                        self.sink.parse_error("<head> in insertion mode InHead".to_owned());
                        true
                    }
                    _ => false,
                }) => Done,
                end!(mut t) if match_atom!(t.name {
                    head => {
                        self.pop();
                        self.mode = states::AfterHead;
                        true
                    }
                    body html br => false,
                    template => fail!("FIXME: <template> not implemented"),
                    _ => {
                        self.sink.parse_error(format!("Unexpected end tag in InHead mode: {}", t));
                        true
                    }
                }) => Done,
                token => if start_named!(token, html) {
                    // Do this here because we can't move out of `token` when it's borrowed.
                    self.process_local(states::InBody, token)
                } else {
                    self.pop();
                    Reprocess(states::AfterHead, token)
                },
            },

            states::InHeadNoscript => match token {
                CharacterTokens(true, text) => {
                    let target = self.target();
                    self.sink.append_text(target, text);
                    Done
                }
                end!(t) if match_atom!(t.name {
                    noscript => {
                        self.pop();
                        self.mode = states::InHead;
                        true
                    }
                    br => false,
                    _ => {
                        self.sink.parse_error(format!("Unexpected end tag in InHeadNoscript mode: {}", t));
                        true
                    }
                }) => Done,
                start!(t) if named!(t, head noscript) => {
                    self.sink.parse_error(format!("Unexpected start tag in InHeadNoscript mode: {}", t));
                    Done
                }

                token @ CommentToken(_) => self.process_local(states::InHead, token),

                token => if start_named!(token, html) {
                    self.process_local(states::InBody, token)
                } else if start_named!(token, basefont bgsound link meta noframes style) {
                    self.process_local(states::InHead, token)
                } else {
                    self.sink.parse_error(format!("Unexpected token in InHeadNoscript mode: {}", token));
                    self.pop();
                    Reprocess(states::InHead, token)
                },
            },

              states::AfterHead
            | states::InBody
            | states::Text
            | states::InTable
            | states::InTableText
            | states::InCaption
            | states::InColumnGroup
            | states::InTableBody
            | states::InRow
            | states::InCell
            | states::InSelect
            | states::InSelectInTable
            | states::InTemplate
            | states::AfterBody
            | states::InFrameset
            | states::AfterFrameset
            | states::AfterAfterBody
            | states::AfterAfterFrameset
                => fail!("not implemented"),
        }
    }
}

impl<'sink, Handle: Clone, Sink: TreeSink<Handle>> TokenSink for TreeBuilder<'sink, Handle, Sink> {
    fn process_token(&mut self, token: tokenizer::Token) {
        // Handle `ParseError` and `DoctypeToken`; convert everything else to the local `Token` type.
        let token = match token {
            tokenizer::ParseError(e) => {
                self.sink.parse_error(e);
                return;
            }

            tokenizer::DoctypeToken(dt) => if self.mode == states::Initial {
                let (err, quirk) = data::doctype_error_and_quirks(&dt, self.opts.iframe_srcdoc);
                if err {
                    self.sink.parse_error(format!("Bad DOCTYPE: {}", dt));
                }
                let Doctype { name, public_id, system_id, force_quirks: _ } = dt;
                self.sink.append_doctype_to_document(
                    name.unwrap_or(StrBuf::new()),
                    public_id.unwrap_or(StrBuf::new()),
                    system_id.unwrap_or(StrBuf::new())
                );
                self.sink.set_quirks_mode(quirk);

                self.mode = states::BeforeHtml;
                return;
            } else {
                self.sink.parse_error(format!("DOCTYPE in insertion mode {:?}", self.mode));
                return;
            },

            tokenizer::TagToken(x) => TagToken(x),
            tokenizer::CommentToken(x) => CommentToken(x),
            tokenizer::CharacterTokens(x) => CharacterTokens(false, x),
            tokenizer::EOFToken => EOFToken,
        };

        // Do we split the token into whitespace / non-whitespace and, if so,
        // do we keep the whitespace or just drop it?
        let (process_whitespace, keep_whitespace) = match self.mode {
            states::InHead | states::InHeadNoscript
                => (true, true),
            states::Initial | states::BeforeHtml | states::BeforeHead
                => (true, false),
            _ => (false, false)
        };

        match (process_whitespace, token) {
            (true, CharacterTokens(_, buf)) => self.process_local_tokens(self.mode,
                Runs::new(is_ascii_whitespace, buf.as_slice())
                    .filter(|&(m, _)| keep_whitespace || !m)
                    .map(|(m, b)| CharacterTokens(m, b.to_strbuf()))),

            (_, token) => self.process_local_tokens(self.mode, Some(token).move_iter()),
        }
    }

    fn query_state_change(&mut self) -> Option<tokenizer::states::State> {
        self.next_tokenizer_state.take()
    }
}
<|endoftext|>


<commit_before>use std::iter;

use hir::Semantics;
use ide_db::{helpers::pick_best_token, RootDatabase};
use itertools::Itertools;
use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T};

use crate::FilePosition;

pub struct ExpandedMacro {
    pub name: String,
    pub expansion: String,
}

// Feature: Expand Macro Recursively
//
// Shows the full macro expansion of the macro at current cursor.
//
// |===
// | Editor  | Action Name
//
// | VS Code | **Rust Analyzer: Expand macro recursively**
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
    let sema = Semantics::new(db);
    let file = sema.parse(position.file_id);

    let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
        SyntaxKind::IDENT => 1,
        _ => 0,
    })?;

    // due to how Rust Analyzer works internally, we need to special case derive attributes,
    // otherwise they might not get found, e.g. here only `#[attr]` would expand:
    // ```
    // #[attr]
    // #[derive($0Foo)]
    // struct Bar;
    // ```

    let derive = sema.descend_into_macros(tok.clone()).iter().find_map(|descended| {
        let attr = descended.ancestors().find_map(ast::Attr::cast)?;
        let (path, tt) = attr.as_simple_call()?;
        if path == "derive" {
            let mut tt = tt.syntax().children_with_tokens().skip(1).join("");
            tt.pop();
            let expansions = sema.expand_derive_macro(&attr)?;
            return Some(ExpandedMacro {
                name: tt,
                expansion: expansions.into_iter().map(insert_whitespaces).join(""),
            });
        } else {
            None
        }
    });

    if derive.is_some() {
        return derive;
    }

    // FIXME: Intermix attribute and bang! expansions
    // currently we only recursively expand one of the two types
    let mut expanded = None;
    let mut name = None;
    for node in tok.ancestors() {
        if let Some(item) = ast::Item::cast(node.clone()) {
            if let Some(def) = sema.resolve_attr_macro_call(&item) {
                name = def.name(db).map(|name| name.to_string());
                expanded = expand_attr_macro_recur(&sema, &item);
                break;
            }
        }
        if let Some(mac) = ast::MacroCall::cast(node) {
            name = Some(mac.path()?.segment()?.name_ref()?.to_string());
            expanded = expand_macro_recur(&sema, &mac);
            break;
        }
    }

    // FIXME:
    // macro expansion may lose all white space information
    // But we hope someday we can use ra_fmt for that
    let expansion = insert_whitespaces(expanded?);
    Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion })
}

fn expand_macro_recur(
    sema: &Semantics<RootDatabase>,
    macro_call: &ast::MacroCall,
) -> Option<SyntaxNode> {
    let expanded = sema.expand(macro_call)?.clone_for_update();
    expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur)
}

fn expand_attr_macro_recur(sema: &Semantics<RootDatabase>, item: &ast::Item) -> Option<SyntaxNode> {
    let expanded = sema.expand_attr_macro(item)?.clone_for_update();
    expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur)
}

fn expand<T: AstNode>(
    sema: &Semantics<RootDatabase>,
    expanded: SyntaxNode,
    f: impl FnMut(SyntaxNode) -> Option<T>,
    exp: impl Fn(&Semantics<RootDatabase>, &T) -> Option<SyntaxNode>,
) -> Option<SyntaxNode> {
    let children = expanded.descendants().filter_map(f);
    let mut replacements = Vec::new();

    for child in children {
        if let Some(new_node) = exp(sema, &child) {
            // check if the whole original syntax is replaced
            if expanded == *child.syntax() {
                return Some(new_node);
            }
            replacements.push((child, new_node));
        }
    }

    replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
    Some(expanded)
}

// FIXME: It would also be cool to share logic here and in the mbe tests,
// which are pretty unreadable at the moment.
fn insert_whitespaces(syn: SyntaxNode) -> String {
    use SyntaxKind::*;
    let mut res = String::new();

    let mut indent = 0;
    let mut last: Option<SyntaxKind> = None;

    for event in syn.preorder_with_tokens() {
        let token = match event {
            WalkEvent::Enter(NodeOrToken::Token(token)) => token,
            WalkEvent::Leave(NodeOrToken::Node(node))
                if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) =>
            {
                res.push('\n');
                res.extend(iter::repeat(" ").take(2 * indent));
                continue;
            }
            _ => continue,
        };
        let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
            token.next_token().map(|it| f(it.kind())).unwrap_or(default)
        };
        let is_last =
            |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };

        match token.kind() {
            k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
                res.push_str(token.text());
                res.push(' ');
            }
            L_CURLY if is_next(|it| it != R_CURLY, true) => {
                indent += 1;
                if is_last(is_text, false) {
                    res.push(' ');
                }
                res.push_str("{\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            R_CURLY if is_last(|it| it != L_CURLY, true) => {
                indent = indent.saturating_sub(1);
                res.push('\n');
                res.extend(iter::repeat(" ").take(2 * indent));
                res.push_str("}");
            }
            R_CURLY => {
                res.push_str("}\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            LIFETIME_IDENT if is_next(|it| it == IDENT || it == MUT_KW, true) => {
                res.push_str(token.text());
                res.push(' ');
            }
            T![;] => {
                res.push_str(";\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            T![->] => res.push_str(" -> "),
            T![=] => res.push_str(" = "),
            T![=>] => res.push_str(" => "),
            _ => res.push_str(token.text()),
        }

        last = Some(token.kind());
    }

    return res;

    fn is_text(k: SyntaxKind) -> bool {
        k.is_keyword() || k.is_literal() || k == IDENT
    }
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};

    use crate::fixture;

    #[track_caller]
    fn check(ra_fixture: &str, expect: Expect) {
        let (analysis, pos) = fixture::position(ra_fixture);
        let expansion = analysis.expand_macro(pos).unwrap().unwrap();
        let actual = format!("{}\n{}", expansion.name, expansion.expansion);
        expect.assert_eq(&actual);
    }

    #[test]
    fn macro_expand_recursive_expansion() {
        check(
            r#"
macro_rules! bar {
    () => { fn  b() {} }
}
macro_rules! foo {
    () => { bar!(); }
}
macro_rules! baz {
    () => { foo!(); }
}
f$0oo!();
"#,
            expect![[r#"
                foo
                fn b(){}

            "#]],
        );
    }

    #[test]
    fn macro_expand_multiple_lines() {
        check(
            r#"
macro_rules! foo {
    () => {
        fn some_thing() -> u32 {
            let a = 0;
            a + 10
        }
    }
}
f$0oo!();
        "#,
            expect![[r#"
                foo
                fn some_thing() -> u32 {
                  let a = 0;
                  a+10
                }
            "#]],
        );
    }

    #[test]
    fn macro_expand_match_ast() {
        check(
            r#"
macro_rules! match_ast {
    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
    (match ($node:expr) {
        $( ast::$ast:ident($it:ident) => $res:block, )*
        _ => $catch_all:expr $(,)?
    }) => {{
        $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
        { $catch_all }
    }};
}

fn main() {
    mat$0ch_ast! {
        match container {
            ast::TraitDef(it) => {},
            ast::ImplDef(it) => {},
            _ => { continue },
        }
    }
}
"#,
            expect![[r#"
       match_ast
       {
         if let Some(it) = ast::TraitDef::cast(container.clone()){}
         else if let Some(it) = ast::ImplDef::cast(container.clone()){}
         else {
           {
             continue
           }
         }
       }"#]],
        );
    }

    #[test]
    fn macro_expand_match_ast_inside_let_statement() {
        check(
            r#"
macro_rules! match_ast {
    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
    (match ($node:expr) {}) => {{}};
}

fn main() {
    let p = f(|it| {
        let res = mat$0ch_ast! { match c {}};
        Some(res)
    })?;
}
"#,
            expect![[r#"
                match_ast
                {}
            "#]],
        );
    }

    #[test]
    fn macro_expand_inner_macro_fail_to_expand() {
        check(
            r#"
macro_rules! bar {
    (BAD) => {};
}
macro_rules! foo {
    () => {bar!()};
}

fn main() {
    let res = fo$0o!();
}
"#,
            expect![[r#"
                foo
            "#]],
        );
    }

    #[test]
    fn macro_expand_with_dollar_crate() {
        check(
            r#"
#[macro_export]
macro_rules! bar {
    () => {0};
}
macro_rules! foo {
    () => {$crate::bar!()};
}

fn main() {
    let res = fo$0o!();
}
"#,
            expect![[r#"
                foo
                0 "#]],
        );
    }

    #[test]
    fn macro_expand_derive() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}

#[doc = ""]
#[derive(C$0lone)]
struct Foo {}
"#,
            expect![[r#"
                Clone
                impl< >crate::clone::Clone for Foo< >{}

            "#]],
        );
    }

    #[test]
    fn macro_expand_derive2() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}

#[derive(Cop$0y)]
#[derive(Clone)]
struct Foo {}
"#,
            expect![[r#"
                Copy
                impl< >crate::marker::Copy for Foo< >{}

            "#]],
        );
    }

    #[test]
    fn macro_expand_derive_multi() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}

#[derive(Cop$0y, Clone)]
struct Foo {}
"#,
            expect![[r#"
                Copy, Clone
                impl< >crate::marker::Copy for Foo< >{}

                impl< >crate::clone::Clone for Foo< >{}

            "#]],
        );
    }
}
<commit_msg>wording<commit_after>use std::iter;

use hir::Semantics;
use ide_db::{helpers::pick_best_token, RootDatabase};
use itertools::Itertools;
use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T};

use crate::FilePosition;

pub struct ExpandedMacro {
    pub name: String,
    pub expansion: String,
}

// Feature: Expand Macro Recursively
//
// Shows the full macro expansion of the macro at current cursor.
//
// |===
// | Editor  | Action Name
//
// | VS Code | **Rust Analyzer: Expand macro recursively**
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
    let sema = Semantics::new(db);
    let file = sema.parse(position.file_id);

    let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
        SyntaxKind::IDENT => 1,
        _ => 0,
    })?;

    // due to how Rust Analyzer works internally, we need to special case derive attributes,
    // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand:
    // ```
    // #[attr]
    // #[derive($0Foo)]
    // struct Bar;
    // ```

    let derive = sema.descend_into_macros(tok.clone()).iter().find_map(|descended| {
        let attr = descended.ancestors().find_map(ast::Attr::cast)?;
        let (path, tt) = attr.as_simple_call()?;
        if path == "derive" {
            let mut tt = tt.syntax().children_with_tokens().skip(1).join("");
            tt.pop();
            let expansions = sema.expand_derive_macro(&attr)?;
            return Some(ExpandedMacro {
                name: tt,
                expansion: expansions.into_iter().map(insert_whitespaces).join(""),
            });
        } else {
            None
        }
    });

    if derive.is_some() {
        return derive;
    }

    // FIXME: Intermix attribute and bang! expansions
    // currently we only recursively expand one of the two types
    let mut expanded = None;
    let mut name = None;
    for node in tok.ancestors() {
        if let Some(item) = ast::Item::cast(node.clone()) {
            if let Some(def) = sema.resolve_attr_macro_call(&item) {
                name = def.name(db).map(|name| name.to_string());
                expanded = expand_attr_macro_recur(&sema, &item);
                break;
            }
        }
        if let Some(mac) = ast::MacroCall::cast(node) {
            name = Some(mac.path()?.segment()?.name_ref()?.to_string());
            expanded = expand_macro_recur(&sema, &mac);
            break;
        }
    }

    // FIXME:
    // macro expansion may lose all white space information
    // But we hope someday we can use ra_fmt for that
    let expansion = insert_whitespaces(expanded?);
    Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion })
}

fn expand_macro_recur(
    sema: &Semantics<RootDatabase>,
    macro_call: &ast::MacroCall,
) -> Option<SyntaxNode> {
    let expanded = sema.expand(macro_call)?.clone_for_update();
    expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur)
}

fn expand_attr_macro_recur(sema: &Semantics<RootDatabase>, item: &ast::Item) -> Option<SyntaxNode> {
    let expanded = sema.expand_attr_macro(item)?.clone_for_update();
    expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur)
}

fn expand<T: AstNode>(
    sema: &Semantics<RootDatabase>,
    expanded: SyntaxNode,
    f: impl FnMut(SyntaxNode) -> Option<T>,
    exp: impl Fn(&Semantics<RootDatabase>, &T) -> Option<SyntaxNode>,
) -> Option<SyntaxNode> {
    let children = expanded.descendants().filter_map(f);
    let mut replacements = Vec::new();

    for child in children {
        if let Some(new_node) = exp(sema, &child) {
            // check if the whole original syntax is replaced
            if expanded == *child.syntax() {
                return Some(new_node);
            }
            replacements.push((child, new_node));
        }
    }

    replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
    Some(expanded)
}

// FIXME: It would also be cool to share logic here and in the mbe tests,
// which are pretty unreadable at the moment.
fn insert_whitespaces(syn: SyntaxNode) -> String {
    use SyntaxKind::*;
    let mut res = String::new();

    let mut indent = 0;
    let mut last: Option<SyntaxKind> = None;

    for event in syn.preorder_with_tokens() {
        let token = match event {
            WalkEvent::Enter(NodeOrToken::Token(token)) => token,
            WalkEvent::Leave(NodeOrToken::Node(node))
                if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) =>
            {
                res.push('\n');
                res.extend(iter::repeat(" ").take(2 * indent));
                continue;
            }
            _ => continue,
        };
        let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
            token.next_token().map(|it| f(it.kind())).unwrap_or(default)
        };
        let is_last =
            |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };

        match token.kind() {
            k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
                res.push_str(token.text());
                res.push(' ');
            }
            L_CURLY if is_next(|it| it != R_CURLY, true) => {
                indent += 1;
                if is_last(is_text, false) {
                    res.push(' ');
                }
                res.push_str("{\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            R_CURLY if is_last(|it| it != L_CURLY, true) => {
                indent = indent.saturating_sub(1);
                res.push('\n');
                res.extend(iter::repeat(" ").take(2 * indent));
                res.push_str("}");
            }
            R_CURLY => {
                res.push_str("}\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            LIFETIME_IDENT if is_next(|it| it == IDENT || it == MUT_KW, true) => {
                res.push_str(token.text());
                res.push(' ');
            }
            T![;] => {
                res.push_str(";\n");
                res.extend(iter::repeat(" ").take(2 * indent));
            }
            T![->] => res.push_str(" -> "),
            T![=] => res.push_str(" = "),
            T![=>] => res.push_str(" => "),
            _ => res.push_str(token.text()),
        }

        last = Some(token.kind());
    }

    return res;

    fn is_text(k: SyntaxKind) -> bool {
        k.is_keyword() || k.is_literal() || k == IDENT
    }
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};

    use crate::fixture;

    #[track_caller]
    fn check(ra_fixture: &str, expect: Expect) {
        let (analysis, pos) = fixture::position(ra_fixture);
        let expansion = analysis.expand_macro(pos).unwrap().unwrap();
        let actual = format!("{}\n{}", expansion.name, expansion.expansion);
        expect.assert_eq(&actual);
    }

    #[test]
    fn macro_expand_recursive_expansion() {
        check(
            r#"
macro_rules! bar {
    () => { fn  b() {} }
}
macro_rules! foo {
    () => { bar!(); }
}
macro_rules! baz {
    () => { foo!(); }
}
f$0oo!();
"#,
            expect![[r#"
                foo
                fn b(){}

            "#]],
        );
    }

    #[test]
    fn macro_expand_multiple_lines() {
        check(
            r#"
macro_rules! foo {
    () => {
        fn some_thing() -> u32 {
            let a = 0;
            a + 10
        }
    }
}
f$0oo!();
        "#,
            expect![[r#"
                foo
                fn some_thing() -> u32 {
                  let a = 0;
                  a+10
                }
            "#]],
        );
    }

    #[test]
    fn macro_expand_match_ast() {
        check(
            r#"
macro_rules! match_ast {
    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
    (match ($node:expr) {
        $( ast::$ast:ident($it:ident) => $res:block, )*
        _ => $catch_all:expr $(,)?
    }) => {{
        $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
        { $catch_all }
    }};
}

fn main() {
    mat$0ch_ast! {
        match container {
            ast::TraitDef(it) => {},
            ast::ImplDef(it) => {},
            _ => { continue },
        }
    }
}
"#,
            expect![[r#"
       match_ast
       {
         if let Some(it) = ast::TraitDef::cast(container.clone()){}
         else if let Some(it) = ast::ImplDef::cast(container.clone()){}
         else {
           {
             continue
           }
         }
       }"#]],
        );
    }

    #[test]
    fn macro_expand_match_ast_inside_let_statement() {
        check(
            r#"
macro_rules! match_ast {
    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
    (match ($node:expr) {}) => {{}};
}

fn main() {
    let p = f(|it| {
        let res = mat$0ch_ast! { match c {}};
        Some(res)
    })?;
}
"#,
            expect![[r#"
                match_ast
                {}
            "#]],
        );
    }

    #[test]
    fn macro_expand_inner_macro_fail_to_expand() {
        check(
            r#"
macro_rules! bar {
    (BAD) => {};
}
macro_rules! foo {
    () => {bar!()};
}

fn main() {
    let res = fo$0o!();
}
"#,
            expect![[r#"
                foo
            "#]],
        );
    }

    #[test]
    fn macro_expand_with_dollar_crate() {
        check(
            r#"
#[macro_export]
macro_rules! bar {
    () => {0};
}
macro_rules! foo {
    () => {$crate::bar!()};
}

fn main() {
    let res = fo$0o!();
}
"#,
            expect![[r#"
                foo
                0 "#]],
        );
    }

    #[test]
    fn macro_expand_derive() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}

#[doc = ""]
#[derive(C$0lone)]
struct Foo {}
"#,
            expect![[r#"
                Clone
                impl< >crate::clone::Clone for Foo< >{}

            "#]],
        );
    }

    #[test]
    fn macro_expand_derive2() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}

#[derive(Cop$0y)]
#[derive(Clone)]
struct Foo {}
"#,
            expect![[r#"
                Copy
                impl< >crate::marker::Copy for Foo< >{}

            "#]],
        );
    }

    #[test]
    fn macro_expand_derive_multi() {
        check(
            r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}

#[derive(Cop$0y, Clone)]
struct Foo {}
"#,
            expect![[r#"
                Copy, Clone
                impl< >crate::marker::Copy for Foo< >{}

                impl< >crate::clone::Clone for Foo< >{}

            "#]],
        );
    }
}
<|endoftext|>


<commit_before>//! rust-analyzer is lazy and doesn't compute anything unless asked. This
//! sometimes is counter productive when, for example, the first goto definition
//! request takes longer to compute. This modules implemented prepopulation of
//! various caches, it's not really advanced at the moment.

use hir::db::DefDatabase;
use ide_db::base_db::SourceDatabase;

use crate::RootDatabase;

/// We started indexing a crate.
#[derive(Debug)]
pub struct PrimeCachesProgress {
    pub on_crate: String,
    pub n_done: usize,
    pub n_total: usize,
}

pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress) + Sync)) {
    let _p = profile::span("prime_caches");
    let graph = db.crate_graph();
    let topo = &graph.crates_in_topological_order();

    // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
    // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
    // cancellation, so we cannot use rayon.
    for (i, &crate_id) in topo.iter().enumerate() {
        let crate_name = graph[crate_id].display_name.as_deref().unwrap_or_default().to_string();

        cb(PrimeCachesProgress { on_crate: crate_name, n_done: i, n_total: topo.len() });
        db.crate_def_map(crate_id);
        db.import_map(crate_id);
    }
}
<commit_msg>Prime a more reasonable set of crates<commit_after>//! rust-analyzer is lazy and doesn't compute anything unless asked. This
//! sometimes is counter productive when, for example, the first goto definition
//! request takes longer to compute. This modules implemented prepopulation of
//! various caches, it's not really advanced at the moment.

use hir::db::DefDatabase;
use ide_db::base_db::{CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt};
use rustc_hash::FxHashSet;

use crate::RootDatabase;

/// We started indexing a crate.
#[derive(Debug)]
pub struct PrimeCachesProgress {
    pub on_crate: String,
    pub n_done: usize,
    pub n_total: usize,
}

pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress) + Sync)) {
    let _p = profile::span("prime_caches");
    let graph = db.crate_graph();
    // We're only interested in the transitive dependencies of all workspace crates.
    let to_prime: FxHashSet<_> = graph
        .iter()
        .filter(|&id| {
            let file_id = graph[id].root_file_id;
            let root_id = db.file_source_root(file_id);
            !db.source_root(root_id).is_library
        })
        .flat_map(|id| graph.transitive_deps(id))
        .collect();

    let topo = toposort(&graph, &to_prime);

    // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
    // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
    // cancellation, so we cannot use rayon.
    for (i, &crate_id) in topo.iter().enumerate() {
        let crate_name = graph[crate_id].display_name.as_deref().unwrap_or_default().to_string();

        cb(PrimeCachesProgress { on_crate: crate_name, n_done: i, n_total: topo.len() });
        db.crate_def_map(crate_id);
        db.import_map(crate_id);
    }
}

fn toposort(graph: &CrateGraph, crates: &FxHashSet<CrateId>) -> Vec<CrateId> {
    // Just subset the full topologically sorted set for simplicity.

    let all = graph.crates_in_topological_order();
    let mut result = Vec::with_capacity(crates.len());
    for krate in all {
        if crates.contains(&krate) {
            result.push(krate);
        }
    }
    result
}
<|endoftext|>


<commit_before>//! This crate contains a single public function
//! [`get_path_for_executable`](fn.get_path_for_executable.html).
//! See docs there for more information.
use std::{env, iter, path::PathBuf};

pub fn cargo() -> PathBuf {
    get_path_for_executable("cargo")
}

pub fn rustc() -> PathBuf {
    get_path_for_executable("rustc")
}

pub fn rustup() -> PathBuf {
    get_path_for_executable("rustup")
}

/// Return a `PathBuf` to use for the given executable.
///
/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that
/// gives a valid Cargo executable; or it may return a full path to a valid
/// Cargo.
fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
    // The current implementation checks three places for an executable to use:
    // 1) Appropriate environment variable (erroring if this is set but not a usable executable)
    //      example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc
    // 2) `<executable_name>`
    //      example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
    // 3) `~/.cargo/bin/<executable_name>`
    //      example: for cargo, this tries ~/.cargo/bin/cargo
    //      It seems that this is a reasonable place to try for cargo, rustc, and rustup
    let env_var = executable_name.to_ascii_uppercase();
    if let Some(path) = env::var_os(&env_var) {
        return path.into();
    }

    if lookup_in_path(executable_name) {
        return executable_name.into();
    }

    if let Some(mut path) = home::home_dir() {
        path.push(".cargo");
        path.push("bin");
        path.push(executable_name);
        if path.is_file() {
            return path;
        }
    }
    executable_name.into()
}

fn lookup_in_path(exec: &str) -> bool {
    let paths = env::var_os("PATH").unwrap_or_default();
    let mut candidates = env::split_paths(&paths).flat_map(|path| {
        let candidate = path.join(&exec);
        let with_exe = if env::consts::EXE_EXTENSION == "" {
            None
        } else {
            Some(candidate.with_extension(env::consts::EXE_EXTENSION))
        };
        iter::once(candidate).chain(with_exe)
    });
    candidates.any(|it| it.is_file())
}
<commit_msg>Simplify match<commit_after>//! This crate contains a single public function
//! [`get_path_for_executable`](fn.get_path_for_executable.html).
//! See docs there for more information.
use std::{env, iter, path::PathBuf};

pub fn cargo() -> PathBuf {
    get_path_for_executable("cargo")
}

pub fn rustc() -> PathBuf {
    get_path_for_executable("rustc")
}

pub fn rustup() -> PathBuf {
    get_path_for_executable("rustup")
}

/// Return a `PathBuf` to use for the given executable.
///
/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that
/// gives a valid Cargo executable; or it may return a full path to a valid
/// Cargo.
fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
    // The current implementation checks three places for an executable to use:
    // 1) Appropriate environment variable (erroring if this is set but not a usable executable)
    //      example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc
    // 2) `<executable_name>`
    //      example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
    // 3) `~/.cargo/bin/<executable_name>`
    //      example: for cargo, this tries ~/.cargo/bin/cargo
    //      It seems that this is a reasonable place to try for cargo, rustc, and rustup
    let env_var = executable_name.to_ascii_uppercase();
    if let Some(path) = env::var_os(&env_var) {
        return path.into();
    }

    if lookup_in_path(executable_name) {
        return executable_name.into();
    }

    if let Some(mut path) = home::home_dir() {
        path.push(".cargo");
        path.push("bin");
        path.push(executable_name);
        if path.is_file() {
            return path;
        }
    }
    executable_name.into()
}

fn lookup_in_path(exec: &str) -> bool {
    let paths = env::var_os("PATH").unwrap_or_default();
    let mut candidates = env::split_paths(&paths).flat_map(|path| {
        let candidate = path.join(&exec);
        let with_exe = match env::consts::EXE_EXTENSION {
            "" => None,
            it => Some(candidate.with_extension(it)),
        };
        iter::once(candidate).chain(with_exe)
    });
    candidates.any(|it| it.is_file())
}
<|endoftext|>


<commit_before>use std::fmt;
use std::task::Poll;

use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::util::lev_distance::lev_distance;
use crate::util::{Config, VersionExt};
use anyhow::Error;

use super::context::Context;
use super::types::{ConflictMap, ConflictReason};

/// Error during resolution providing a path of `PackageId`s.
pub struct ResolveError {
    cause: Error,
    package_path: Vec<PackageId>,
}

impl ResolveError {
    pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
        Self {
            cause: cause.into(),
            package_path,
        }
    }

    /// Returns a path of packages from the package whose requirements could not be resolved up to
    /// the root.
    pub fn package_path(&self) -> &[PackageId] {
        &self.package_path
    }
}

impl std::error::Error for ResolveError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.cause.source()
    }
}

impl fmt::Debug for ResolveError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.cause.fmt(f)
    }
}

impl fmt::Display for ResolveError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.cause.fmt(f)
    }
}

pub type ActivateResult<T> = Result<T, ActivateError>;

#[derive(Debug)]
pub enum ActivateError {
    Fatal(anyhow::Error),
    Conflict(PackageId, ConflictReason),
}

impl From<::anyhow::Error> for ActivateError {
    fn from(t: ::anyhow::Error) -> Self {
        ActivateError::Fatal(t)
    }
}

impl From<(PackageId, ConflictReason)> for ActivateError {
    fn from(t: (PackageId, ConflictReason)) -> Self {
        ActivateError::Conflict(t.0, t.1)
    }
}

pub(super) fn activation_error(
    cx: &Context,
    registry: &mut dyn Registry,
    parent: &Summary,
    dep: &Dependency,
    conflicting_activations: &ConflictMap,
    candidates: &[Summary],
    config: Option<&Config>,
) -> ResolveError {
    let to_resolve_err = |err| {
        ResolveError::new(
            err,
            cx.parents
                .path_to_bottom(&parent.package_id())
                .into_iter()
                .map(|(node, _)| node)
                .cloned()
                .collect(),
        )
    };

    if !candidates.is_empty() {
        let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
        msg.push_str("\n    ... required by ");
        msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

        msg.push_str("\nversions that meet the requirements `");
        msg.push_str(&dep.version_req().to_string());
        msg.push_str("` ");

        if let Some(v) = dep.version_req().locked_version() {
            msg.push_str("(locked to ");
            msg.push_str(&v.to_string());
            msg.push_str(") ");
        }

        msg.push_str("are: ");
        msg.push_str(
            &candidates
                .iter()
                .map(|v| v.version())
                .map(|v| v.to_string())
                .collect::<Vec<_>>()
                .join(", "),
        );

        let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
        conflicting_activations.sort_unstable();
        // This is reversed to show the newest versions first. I don't know if there is
        // a strong reason to do this, but that is how the code previously worked
        // (see https://github.com/rust-lang/cargo/pull/5037) and I don't feel like changing it.
        conflicting_activations.reverse();
        // Flag used for grouping all semver errors together.
        let mut has_semver = false;

        for (p, r) in &conflicting_activations {
            match r {
                ConflictReason::Semver => {
                    has_semver = true;
                }
                ConflictReason::Links(link) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` links to the native library `");
                    msg.push_str(link);
                    msg.push_str("`, but it conflicts with a previous package which links to `");
                    msg.push_str(link);
                    msg.push_str("` as well:\n");
                    msg.push_str(&describe_path_in_context(cx, p));
                    msg.push_str("\nOnly one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. ");
                    msg.push_str("Try to adjust your dependencies so that only one package uses the links ='");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.");
                }
                ConflictReason::MissingFeatures(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    // p == parent so the full path is redundant.
                }
                ConflictReason::RequiredDependencyAsFeature(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    msg.push_str(
                        " It has a required dependency with that name, \
                         but only optional dependencies can be used as features.\n",
                    );
                    // p == parent so the full path is redundant.
                }
                ConflictReason::NonImplicitDependencyAsFeature(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    msg.push_str(
                        " It has an optional dependency with that name, \
                         but but that dependency uses the \"dep:\" \
                         syntax in the features table, so it does not have an \
                         implicit feature with that name.\n",
                    );
                    // p == parent so the full path is redundant.
                }
                ConflictReason::PublicDependency(pkg_id) => {
                    // TODO: This needs to be implemented.
                    unimplemented!("pub dep {:?}", pkg_id);
                }
                ConflictReason::PubliclyExports(pkg_id) => {
                    // TODO: This needs to be implemented.
                    unimplemented!("pub exp {:?}", pkg_id);
                }
            }
        }

        if has_semver {
            // Group these errors together.
            msg.push_str("\n\nall possible versions conflict with previously selected packages.");
            for (p, r) in &conflicting_activations {
                if let ConflictReason::Semver = r {
                    msg.push_str("\n\n  previously selected ");
                    msg.push_str(&describe_path_in_context(cx, p));
                }
            }
        }

        msg.push_str("\n\nfailed to select a version for `");
        msg.push_str(&*dep.package_name());
        msg.push_str("` which could resolve this conflict");

        return to_resolve_err(anyhow::format_err!("{}", msg));
    }

    // We didn't actually find any candidates, so we need to
    // give an error message that nothing was found.
    //
    // Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
    // was meant. So we re-query the registry with `dep="*"` so we can
    // list a few versions that were actually found.
    let all_req = semver::VersionReq::parse("*").unwrap();
    let mut new_dep = dep.clone();
    new_dep.set_version_req(all_req);

    let mut candidates = loop {
        match registry.query_vec(&new_dep, false) {
            Poll::Ready(Ok(candidates)) => break candidates,
            Poll::Ready(Err(e)) => return to_resolve_err(e),
            Poll::Pending => match registry.block_until_ready() {
                Ok(()) => continue,
                Err(e) => return to_resolve_err(e),
            },
        }
    };

    candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));

    let mut msg =
        if !candidates.is_empty() {
            let versions = {
                let mut versions = candidates
                    .iter()
                    .take(3)
                    .map(|cand| cand.version().to_string())
                    .collect::<Vec<_>>();

                if candidates.len() > 3 {
                    versions.push("...".into());
                }

                versions.join(", ")
            };

            let locked_version = dep
                .version_req()
                .locked_version()
                .map(|v| format!(" (locked to {})", v))
                .unwrap_or_default();

            let mut msg = format!(
                "failed to select a version for the requirement `{} = \"{}\"`{}\n\
                 candidate versions found which didn't match: {}\n\
                 location searched: {}\n",
                dep.package_name(),
                dep.version_req(),
                locked_version,
                versions,
                registry.describe_source(dep.source_id()),
            );
            msg.push_str("required by ");
            msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

            // If we have a path dependency with a locked version, then this may
            // indicate that we updated a sub-package and forgot to run `cargo
            // update`. In this case try to print a helpful error!
            if dep.source_id().is_path() && dep.version_req().is_locked() {
                msg.push_str(
                    "\nconsider running `cargo update` to update \
                     a path dependency's locked version",
                );
            }

            if registry.is_replaced(dep.source_id()) {
                msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?");
            }

            msg
        } else {
            // Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
            // was meant. So we try asking the registry for a `fuzzy` search for suggestions.
            let mut candidates = loop {
                match registry.query_vec(&new_dep, true) {
                    Poll::Ready(Ok(candidates)) => break candidates,
                    Poll::Ready(Err(e)) => return to_resolve_err(e),
                    Poll::Pending => match registry.block_until_ready() {
                        Ok(()) => continue,
                        Err(e) => return to_resolve_err(e),
                    },
                }
            };

            candidates.sort_unstable_by_key(|a| a.name());
            candidates.dedup_by(|a, b| a.name() == b.name());
            let mut candidates: Vec<_> = candidates
                .iter()
                .map(|n| (lev_distance(&*new_dep.package_name(), &*n.name()), n))
                .filter(|&(d, _)| d < 4)
                .collect();
            candidates.sort_by_key(|o| o.0);
            let mut msg: String;
            if candidates.is_empty() {
                msg = format!("no matching package named `{}` found\n", dep.package_name());
            } else {
                msg = format!(
                    "no matching package found\nsearched package name: `{}`\n",
                    dep.package_name()
                );

                // If dependency package name is equal to the name of the candidate here
                // it may be a prerelease package which hasn't been specified correctly
                if dep.package_name() == candidates[0].1.name()
                    && candidates[0].1.package_id().version().is_prerelease()
                {
                    msg.push_str("prerelease package needs to be specified explicitly\n");
                    msg.push_str(&format!(
                        "{name} = {{ version = \"{version}\" }}",
                        name = candidates[0].1.name(),
                        version = candidates[0].1.package_id().version()
                    ));
                } else {
                    let mut names = candidates
                        .iter()
                        .take(3)
                        .map(|c| c.1.name().as_str())
                        .collect::<Vec<_>>();

                    if candidates.len() > 3 {
                        names.push("...");
                    }
                    // Vertically align first suggestion with missing crate name
                    // so a typo jumps out at you.
                    msg.push_str("perhaps you meant:      ");
                    msg.push_str(&names.iter().enumerate().fold(
                        String::default(),
                        |acc, (i, el)| match i {
                            0 => acc + el,
                            i if names.len() - 1 == i && candidates.len() <= 3 => acc + " or " + el,
                            _ => acc + ", " + el,
                        },
                    ));
                }
                msg.push('\n');
            }
            msg.push_str(&format!("location searched: {}\n", dep.source_id()));
            msg.push_str("required by ");
            msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

            msg
        };

    if let Some(config) = config {
        if config.offline() {
            msg.push_str(
                "\nAs a reminder, you're using offline mode (--offline) \
                 which can sometimes cause surprising resolution failures, \
                 if this error is too confusing you may wish to retry \
                 without the offline flag.",
            );
        }
    }

    to_resolve_err(anyhow::format_err!("{}", msg))
}

/// Returns String representation of dependency chain for a particular `pkgid`
/// within given context.
pub(super) fn describe_path_in_context(cx: &Context, id: &PackageId) -> String {
    let iter = cx
        .parents
        .path_to_bottom(id)
        .into_iter()
        .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
    describe_path(iter)
}

/// Returns String representation of dependency chain for a particular `pkgid`.
///
/// Note that all elements of `path` iterator should have `Some` dependency
/// except the first one. It would look like:
///
/// (pkg0, None)
/// -> (pkg1, dep from pkg1 satisfied by pkg0)
/// -> (pkg2, dep from pkg2 satisfied by pkg1)
/// -> ...
pub(crate) fn describe_path<'a>(
    mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
) -> String {
    use std::fmt::Write;

    if let Some(p) = path.next() {
        let mut dep_path_desc = format!("package `{}`", p.0);
        for (pkg, dep) in path {
            let dep = dep.unwrap();
            let source_kind = if dep.source_id().is_path() {
                "path "
            } else if dep.source_id().is_git() {
                "git "
            } else {
                ""
            };
            let requirement = if source_kind.is_empty() {
                format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
            } else {
                dep.name_in_toml().to_string()
            };
            let locked_version = dep
                .version_req()
                .locked_version()
                .map(|v| format!("(locked to {}) ", v))
                .unwrap_or_default();

            write!(
                dep_path_desc,
                "\n    ... which satisfies {}dependency `{}` {}of package `{}`",
                source_kind, requirement, locked_version, pkg
            )
            .unwrap();
        }

        return dep_path_desc;
    }

    String::new()
}
<commit_msg>Auto merge of #10769 - jonhoo:patch-1, r=Eh2406<commit_after>use std::fmt;
use std::task::Poll;

use crate::core::{Dependency, PackageId, Registry, Summary};
use crate::util::lev_distance::lev_distance;
use crate::util::{Config, VersionExt};
use anyhow::Error;

use super::context::Context;
use super::types::{ConflictMap, ConflictReason};

/// Error during resolution providing a path of `PackageId`s.
pub struct ResolveError {
    cause: Error,
    package_path: Vec<PackageId>,
}

impl ResolveError {
    pub fn new<E: Into<Error>>(cause: E, package_path: Vec<PackageId>) -> Self {
        Self {
            cause: cause.into(),
            package_path,
        }
    }

    /// Returns a path of packages from the package whose requirements could not be resolved up to
    /// the root.
    pub fn package_path(&self) -> &[PackageId] {
        &self.package_path
    }
}

impl std::error::Error for ResolveError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.cause.source()
    }
}

impl fmt::Debug for ResolveError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.cause.fmt(f)
    }
}

impl fmt::Display for ResolveError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.cause.fmt(f)
    }
}

pub type ActivateResult<T> = Result<T, ActivateError>;

#[derive(Debug)]
pub enum ActivateError {
    Fatal(anyhow::Error),
    Conflict(PackageId, ConflictReason),
}

impl From<::anyhow::Error> for ActivateError {
    fn from(t: ::anyhow::Error) -> Self {
        ActivateError::Fatal(t)
    }
}

impl From<(PackageId, ConflictReason)> for ActivateError {
    fn from(t: (PackageId, ConflictReason)) -> Self {
        ActivateError::Conflict(t.0, t.1)
    }
}

pub(super) fn activation_error(
    cx: &Context,
    registry: &mut dyn Registry,
    parent: &Summary,
    dep: &Dependency,
    conflicting_activations: &ConflictMap,
    candidates: &[Summary],
    config: Option<&Config>,
) -> ResolveError {
    let to_resolve_err = |err| {
        ResolveError::new(
            err,
            cx.parents
                .path_to_bottom(&parent.package_id())
                .into_iter()
                .map(|(node, _)| node)
                .cloned()
                .collect(),
        )
    };

    if !candidates.is_empty() {
        let mut msg = format!("failed to select a version for `{}`.", dep.package_name());
        msg.push_str("\n    ... required by ");
        msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

        msg.push_str("\nversions that meet the requirements `");
        msg.push_str(&dep.version_req().to_string());
        msg.push_str("` ");

        if let Some(v) = dep.version_req().locked_version() {
            msg.push_str("(locked to ");
            msg.push_str(&v.to_string());
            msg.push_str(") ");
        }

        msg.push_str("are: ");
        msg.push_str(
            &candidates
                .iter()
                .map(|v| v.version())
                .map(|v| v.to_string())
                .collect::<Vec<_>>()
                .join(", "),
        );

        let mut conflicting_activations: Vec<_> = conflicting_activations.iter().collect();
        conflicting_activations.sort_unstable();
        // This is reversed to show the newest versions first. I don't know if there is
        // a strong reason to do this, but that is how the code previously worked
        // (see https://github.com/rust-lang/cargo/pull/5037) and I don't feel like changing it.
        conflicting_activations.reverse();
        // Flag used for grouping all semver errors together.
        let mut has_semver = false;

        for (p, r) in &conflicting_activations {
            match r {
                ConflictReason::Semver => {
                    has_semver = true;
                }
                ConflictReason::Links(link) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` links to the native library `");
                    msg.push_str(link);
                    msg.push_str("`, but it conflicts with a previous package which links to `");
                    msg.push_str(link);
                    msg.push_str("` as well:\n");
                    msg.push_str(&describe_path_in_context(cx, p));
                    msg.push_str("\nOnly one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. ");
                    msg.push_str("Try to adjust your dependencies so that only one package uses the links ='");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.");
                }
                ConflictReason::MissingFeatures(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    // p == parent so the full path is redundant.
                }
                ConflictReason::RequiredDependencyAsFeature(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    msg.push_str(
                        " It has a required dependency with that name, \
                         but only optional dependencies can be used as features.\n",
                    );
                    // p == parent so the full path is redundant.
                }
                ConflictReason::NonImplicitDependencyAsFeature(features) => {
                    msg.push_str("\n\nthe package `");
                    msg.push_str(&*p.name());
                    msg.push_str("` depends on `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("`, with features: `");
                    msg.push_str(features);
                    msg.push_str("` but `");
                    msg.push_str(&*dep.package_name());
                    msg.push_str("` does not have these features.\n");
                    msg.push_str(
                        " It has an optional dependency with that name, \
                         but that dependency uses the \"dep:\" \
                         syntax in the features table, so it does not have an \
                         implicit feature with that name.\n",
                    );
                    // p == parent so the full path is redundant.
                }
                ConflictReason::PublicDependency(pkg_id) => {
                    // TODO: This needs to be implemented.
                    unimplemented!("pub dep {:?}", pkg_id);
                }
                ConflictReason::PubliclyExports(pkg_id) => {
                    // TODO: This needs to be implemented.
                    unimplemented!("pub exp {:?}", pkg_id);
                }
            }
        }

        if has_semver {
            // Group these errors together.
            msg.push_str("\n\nall possible versions conflict with previously selected packages.");
            for (p, r) in &conflicting_activations {
                if let ConflictReason::Semver = r {
                    msg.push_str("\n\n  previously selected ");
                    msg.push_str(&describe_path_in_context(cx, p));
                }
            }
        }

        msg.push_str("\n\nfailed to select a version for `");
        msg.push_str(&*dep.package_name());
        msg.push_str("` which could resolve this conflict");

        return to_resolve_err(anyhow::format_err!("{}", msg));
    }

    // We didn't actually find any candidates, so we need to
    // give an error message that nothing was found.
    //
    // Maybe the user mistyped the ver_req? Like `dep="2"` when `dep="0.2"`
    // was meant. So we re-query the registry with `dep="*"` so we can
    // list a few versions that were actually found.
    let all_req = semver::VersionReq::parse("*").unwrap();
    let mut new_dep = dep.clone();
    new_dep.set_version_req(all_req);

    let mut candidates = loop {
        match registry.query_vec(&new_dep, false) {
            Poll::Ready(Ok(candidates)) => break candidates,
            Poll::Ready(Err(e)) => return to_resolve_err(e),
            Poll::Pending => match registry.block_until_ready() {
                Ok(()) => continue,
                Err(e) => return to_resolve_err(e),
            },
        }
    };

    candidates.sort_unstable_by(|a, b| b.version().cmp(a.version()));

    let mut msg =
        if !candidates.is_empty() {
            let versions = {
                let mut versions = candidates
                    .iter()
                    .take(3)
                    .map(|cand| cand.version().to_string())
                    .collect::<Vec<_>>();

                if candidates.len() > 3 {
                    versions.push("...".into());
                }

                versions.join(", ")
            };

            let locked_version = dep
                .version_req()
                .locked_version()
                .map(|v| format!(" (locked to {})", v))
                .unwrap_or_default();

            let mut msg = format!(
                "failed to select a version for the requirement `{} = \"{}\"`{}\n\
                 candidate versions found which didn't match: {}\n\
                 location searched: {}\n",
                dep.package_name(),
                dep.version_req(),
                locked_version,
                versions,
                registry.describe_source(dep.source_id()),
            );
            msg.push_str("required by ");
            msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

            // If we have a path dependency with a locked version, then this may
            // indicate that we updated a sub-package and forgot to run `cargo
            // update`. In this case try to print a helpful error!
            if dep.source_id().is_path() && dep.version_req().is_locked() {
                msg.push_str(
                    "\nconsider running `cargo update` to update \
                     a path dependency's locked version",
                );
            }

            if registry.is_replaced(dep.source_id()) {
                msg.push_str("\nperhaps a crate was updated and forgotten to be re-vendored?");
            }

            msg
        } else {
            // Maybe the user mistyped the name? Like `dep-thing` when `Dep_Thing`
            // was meant. So we try asking the registry for a `fuzzy` search for suggestions.
            let mut candidates = loop {
                match registry.query_vec(&new_dep, true) {
                    Poll::Ready(Ok(candidates)) => break candidates,
                    Poll::Ready(Err(e)) => return to_resolve_err(e),
                    Poll::Pending => match registry.block_until_ready() {
                        Ok(()) => continue,
                        Err(e) => return to_resolve_err(e),
                    },
                }
            };

            candidates.sort_unstable_by_key(|a| a.name());
            candidates.dedup_by(|a, b| a.name() == b.name());
            let mut candidates: Vec<_> = candidates
                .iter()
                .map(|n| (lev_distance(&*new_dep.package_name(), &*n.name()), n))
                .filter(|&(d, _)| d < 4)
                .collect();
            candidates.sort_by_key(|o| o.0);
            let mut msg: String;
            if candidates.is_empty() {
                msg = format!("no matching package named `{}` found\n", dep.package_name());
            } else {
                msg = format!(
                    "no matching package found\nsearched package name: `{}`\n",
                    dep.package_name()
                );

                // If dependency package name is equal to the name of the candidate here
                // it may be a prerelease package which hasn't been specified correctly
                if dep.package_name() == candidates[0].1.name()
                    && candidates[0].1.package_id().version().is_prerelease()
                {
                    msg.push_str("prerelease package needs to be specified explicitly\n");
                    msg.push_str(&format!(
                        "{name} = {{ version = \"{version}\" }}",
                        name = candidates[0].1.name(),
                        version = candidates[0].1.package_id().version()
                    ));
                } else {
                    let mut names = candidates
                        .iter()
                        .take(3)
                        .map(|c| c.1.name().as_str())
                        .collect::<Vec<_>>();

                    if candidates.len() > 3 {
                        names.push("...");
                    }
                    // Vertically align first suggestion with missing crate name
                    // so a typo jumps out at you.
                    msg.push_str("perhaps you meant:      ");
                    msg.push_str(&names.iter().enumerate().fold(
                        String::default(),
                        |acc, (i, el)| match i {
                            0 => acc + el,
                            i if names.len() - 1 == i && candidates.len() <= 3 => acc + " or " + el,
                            _ => acc + ", " + el,
                        },
                    ));
                }
                msg.push('\n');
            }
            msg.push_str(&format!("location searched: {}\n", dep.source_id()));
            msg.push_str("required by ");
            msg.push_str(&describe_path_in_context(cx, &parent.package_id()));

            msg
        };

    if let Some(config) = config {
        if config.offline() {
            msg.push_str(
                "\nAs a reminder, you're using offline mode (--offline) \
                 which can sometimes cause surprising resolution failures, \
                 if this error is too confusing you may wish to retry \
                 without the offline flag.",
            );
        }
    }

    to_resolve_err(anyhow::format_err!("{}", msg))
}

/// Returns String representation of dependency chain for a particular `pkgid`
/// within given context.
pub(super) fn describe_path_in_context(cx: &Context, id: &PackageId) -> String {
    let iter = cx
        .parents
        .path_to_bottom(id)
        .into_iter()
        .map(|(p, d)| (p, d.and_then(|d| d.iter().next())));
    describe_path(iter)
}

/// Returns String representation of dependency chain for a particular `pkgid`.
///
/// Note that all elements of `path` iterator should have `Some` dependency
/// except the first one. It would look like:
///
/// (pkg0, None)
/// -> (pkg1, dep from pkg1 satisfied by pkg0)
/// -> (pkg2, dep from pkg2 satisfied by pkg1)
/// -> ...
pub(crate) fn describe_path<'a>(
    mut path: impl Iterator<Item = (&'a PackageId, Option<&'a Dependency>)>,
) -> String {
    use std::fmt::Write;

    if let Some(p) = path.next() {
        let mut dep_path_desc = format!("package `{}`", p.0);
        for (pkg, dep) in path {
            let dep = dep.unwrap();
            let source_kind = if dep.source_id().is_path() {
                "path "
            } else if dep.source_id().is_git() {
                "git "
            } else {
                ""
            };
            let requirement = if source_kind.is_empty() {
                format!("{} = \"{}\"", dep.name_in_toml(), dep.version_req())
            } else {
                dep.name_in_toml().to_string()
            };
            let locked_version = dep
                .version_req()
                .locked_version()
                .map(|v| format!("(locked to {}) ", v))
                .unwrap_or_default();

            write!(
                dep_path_desc,
                "\n    ... which satisfies {}dependency `{}` {}of package `{}`",
                source_kind, requirement, locked_version, pkg
            )
            .unwrap();
        }

        return dep_path_desc;
    }

    String::new()
}
<|endoftext|>