<commit_before>use encoding::label::encoding_from_whatwg_label;
use encoding::DecoderTrap;
use libc;
use std;
use std::mem;
use std::ptr;
use std::rc::Rc;
use std::str;

use attributes::Attribute;
use css;
use handle::{self, RsvgHandle};
use load::rsvg_load_new_node;
use node::{node_new, Node, NodeType};
use property_bag::PropertyBag;
use structure::NodeSvg;
use style::NodeStyle;
use text::NodeChars;
use tree::{RsvgTree, Tree};
use util::utf8_cstr;

enum ContextKind {
    // Starting state
    Start,

    // Creating nodes for elements under the current node
    ElementCreation,

    // Inside <xi:include>
    XInclude(XIncludeContext),

    // An unsupported element inside a <xi:include> context, to be ignored
    UnsupportedXIncludeChild,

    // Insie <xi::fallback>
    XIncludeFallback(XIncludeContext),
}

#[derive(Clone)]
struct XIncludeContext {
    need_fallback: bool,
}

/// A concrete parsing context for a surrounding `element_name` and its XML event handlers
struct Context {
    element_name: String,
    kind: ContextKind,
}

impl Context {
    fn empty() -> Context {
        Context {
            element_name: String::new(),
            kind: ContextKind::Start,
        }
    }
}

// A *const RsvgXmlState is just the type that we export to C
pub enum RsvgXmlState {}

/// Holds the state used for XML processing
///
/// These methods are called when an XML event is parsed out of the XML stream: `start_element`,
/// `end_element`, `characters`.
///
/// When an element starts, we push a corresponding `Context` into the `context_stack`.  Within
/// that context, all XML events will be forwarded to it, and processed in one of the `XmlHandler`
/// trait objects. Normally the context refers to a `NodeCreationContext` implementation which is
/// what creates normal graphical elements.
struct XmlState {
    tree: Option<Box<Tree>>,
    context: Context,
    context_stack: Vec<Context>,
    current_node: Option<Rc<Node>>,
}

impl XmlState {
    fn new() -> XmlState {
        XmlState {
            tree: None,
            context: Context::empty(),
            context_stack: Vec::new(),
            current_node: None,
        }
    }

    pub fn set_root(&mut self, root: &Rc<Node>) {
        if self.tree.is_some() {
            panic!("The tree root has already been set");
        }

        self.tree = Some(Box::new(Tree::new(root)));
    }

    pub fn steal_tree(&mut self) -> Option<Box<Tree>> {
        self.tree.take()
    }

    fn push_context(&mut self, ctx: Context) {
        let top = mem::replace(&mut self.context, ctx);
        self.context_stack.push(top);
    }

    pub fn start_element(&mut self, handle: *mut RsvgHandle, name: &str, pbag: &PropertyBag) {
        let ctx = mem::replace(&mut self.context, Context::empty());

        let new_ctx = match ctx.kind {
            ContextKind::Start => self.element_creation_start_element(handle, name, pbag),
            ContextKind::ElementCreation => self.element_creation_start_element(handle, name, pbag),
            ContextKind::XInclude(ref ctx) => self.inside_xinclude_start_element(ctx, name),
            ContextKind::UnsupportedXIncludeChild => self.unsupported_xinclude_start_element(name),
            ContextKind::XIncludeFallback(ref ctx) => {
                self.xinclude_fallback_start_element(&ctx.clone(), handle, name, pbag)
            }
        };

        mem::replace(&mut self.context, ctx);
        self.push_context(new_ctx);
    }

    pub fn end_element(&mut self, handle: *mut RsvgHandle, name: &str) {
        // We can unwrap since start_element() always adds a context to the stack
        let top = self.context_stack.pop().unwrap();

        let context = mem::replace(&mut self.context, top);

        assert!(context.element_name == name);

        match context.kind {
            ContextKind::Start => panic!("end_element: XML handler stack is empty!?"),
            ContextKind::ElementCreation => self.element_creation_end_element(handle),
            ContextKind::XInclude(_) => (),
            ContextKind::UnsupportedXIncludeChild => (),
            ContextKind::XIncludeFallback(_) => (),
        }
    }

    pub fn characters(&mut self, text: &str) {
        let ctx = mem::replace(&mut self.context, Context::empty());

        match ctx.kind {
            ContextKind::Start => panic!("characters: XML handler stack is empty!?"),
            ContextKind::ElementCreation => self.element_creation_characters(text),
            ContextKind::XInclude(_) => (),
            ContextKind::UnsupportedXIncludeChild => (),
            ContextKind::XIncludeFallback(ref ctx) => {
                self.xinclude_fallback_characters(&ctx.clone(), text)
            }
        }

        mem::replace(&mut self.context, ctx);
    }

    fn element_creation_start_element(
        &mut self,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        match name {
            "include" => self.xinclude_start_element(handle, name, pbag),
            _ => {
                let node = self.create_node(self.current_node.as_ref(), handle, name, pbag);
                if self.current_node.is_none() {
                    self.set_root(&node);
                }
                self.current_node = Some(node);

                Context {
                    element_name: name.to_string(),
                    kind: ContextKind::ElementCreation,
                }
            }
        }
    }

    fn element_creation_end_element(&mut self, handle: *mut RsvgHandle) {
        let node = self.current_node.take().unwrap();

        // The "svg" node is special; it parses its style attributes
        // here, not during element creation.
        if node.get_type() == NodeType::Svg {
            node.with_impl(|svg: &NodeSvg| {
                svg.set_delayed_style(&node, handle);
            });
        }

        if node.get_type() == NodeType::Style {
            let css_data = node.with_impl(|style: &NodeStyle| style.get_css(&node));

            css::parse_into_handle(handle, &css_data);
        }

        self.current_node = node.get_parent();
    }

    fn element_creation_characters(&self, text: &str) {
        let node = self.current_node.as_ref().unwrap();

        if text.len() != 0 && node.accept_chars() {
            let chars_node = if let Some(child) = node.find_last_chars_child() {
                child
            } else {
                let child = node_new(
                    NodeType::Chars,
                    Some(node),
                    "rsvg-chars",
                    None,
                    None,
                    Box::new(NodeChars::new()),
                );
                node.add_child(&child);
                child
            };

            chars_node.with_impl(|chars: &NodeChars| {
                chars.append(text);
            });
        }
    }

    fn create_node(
        &self,
        parent: Option<&Rc<Node>>,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Rc<Node> {
        let mut defs = handle::get_defs(handle);

        let new_node = rsvg_load_new_node(name, parent, pbag, &mut defs);

        if let Some(parent) = parent {
            parent.add_child(&new_node);
        }

        new_node.set_atts(&new_node, handle, pbag);

        // The "svg" node is special; it will parse its style attributes
        // until the end, in standard_element_end().
        if new_node.get_type() != NodeType::Svg {
            new_node.set_style(handle, pbag);
        }

        new_node.set_overridden_properties();

        new_node
    }

    fn xinclude_start_element(
        &mut self,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        let mut href = None;
        let mut parse = None;
        let mut encoding = None;

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::Href => href = Some(value),
                Attribute::Parse => parse = Some(value),
                Attribute::Encoding => encoding = Some(value),
                _ => (),
            }
        }

        let need_fallback = !self.acquire(handle, href, parse, encoding).is_ok();

        Context {
            element_name: name.to_string(),
            kind: ContextKind::XInclude(XIncludeContext { need_fallback }),
        }
    }

    fn inside_xinclude_start_element(&self, ctx: &XIncludeContext, name: &str) -> Context {
        // FIXME: we aren't using the xi: namespace
        if name == "fallback" {
            Context {
                element_name: name.to_string(),
                kind: ContextKind::XIncludeFallback(ctx.clone()),
            }
        } else {
            self.unsupported_xinclude_start_element(name)
        }
    }

    fn xinclude_fallback_start_element(
        &mut self,
        ctx: &XIncludeContext,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        if ctx.need_fallback {
            // FIXME: we aren't using the xi: namespace
            if name == "include" {
                self.xinclude_start_element(handle, name, pbag)
            } else {
                self.element_creation_start_element(handle, name, pbag)
            }
        } else {
            Context {
                element_name: name.to_string(),
                kind: ContextKind::UnsupportedXIncludeChild,
            }
        }
    }

    fn xinclude_fallback_characters(&mut self, ctx: &XIncludeContext, text: &str) {
        if ctx.need_fallback {
            self.element_creation_characters(text);
        }
    }

    fn acquire(
        &mut self,
        handle: *mut RsvgHandle,
        href: Option<&str>,
        parse: Option<&str>,
        encoding: Option<&str>,
    ) -> Result<(), ()> {
        if let Some(href) = href {
            if parse == Some("text") {
                self.acquire_text(handle, href, encoding)
            } else {
                self.acquire_xml(handle, href)
            }
        } else {
            Err(())
        }
    }

    fn acquire_text(
        &mut self,
        handle: *mut RsvgHandle,
        href: &str,
        encoding: Option<&str>,
    ) -> Result<(), ()> {
        let binary = handle::acquire_data(handle, href).map_err(|e| {
            rsvg_log!("could not acquire \"{}\": {}", href, e);
            ()
        })?;

        let encoding = encoding.unwrap_or("utf-8");

        let encoder = encoding_from_whatwg_label(encoding).ok_or_else(|| {
            rsvg_log!("unknown encoding \"{}\" for \"{}\"", encoding, href);
            ()
        })?;

        let utf8_data = encoder
            .decode(&binary.data, DecoderTrap::Strict)
            .map_err(|e| {
                rsvg_log!(
                    "could not convert contents of \"{}\" from character encoding \"{}\": {}",
                    href,
                    encoding,
                    e
                );
                ()
            })?;

        self.element_creation_characters(&utf8_data);
        Ok(())
    }

    fn acquire_xml(&self, handle: *mut RsvgHandle, href: &str) -> Result<(), ()> {
        if handle::load_xml_xinclude(handle, href) {
            Ok(())
        } else {
            Err(())
        }
    }

    fn unsupported_xinclude_start_element(&self, name: &str) -> Context {
        Context {
            element_name: name.to_string(),
            kind: ContextKind::UnsupportedXIncludeChild,
        }
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_new() -> *mut RsvgXmlState {
    Box::into_raw(Box::new(XmlState::new())) as *mut RsvgXmlState
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_free(xml: *mut RsvgXmlState) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };
    unsafe {
        Box::from_raw(xml);
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_steal_tree(xml: *mut RsvgXmlState) -> *mut RsvgTree {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    if let Some(tree) = xml.steal_tree() {
        Box::into_raw(tree) as *mut RsvgTree
    } else {
        ptr::null_mut()
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_start_element(
    xml: *mut RsvgXmlState,
    handle: *mut RsvgHandle,
    name: *const libc::c_char,
    pbag: *const PropertyBag,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!name.is_null());
    let name = unsafe { utf8_cstr(name) };

    assert!(!pbag.is_null());
    let pbag = unsafe { &*pbag };

    xml.start_element(handle, name, pbag);
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_end_element(
    xml: *mut RsvgXmlState,
    handle: *mut RsvgHandle,
    name: *const libc::c_char,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!name.is_null());
    let name = unsafe { utf8_cstr(name) };

    xml.end_element(handle, name);
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_characters(
    xml: *mut RsvgXmlState,
    unterminated_text: *const libc::c_char,
    len: usize,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!unterminated_text.is_null());

    // libxml2 already validated the incoming string as UTF-8.  Note that
    // it is *not* nul-terminated; this is why we create a byte slice first.
    let bytes = unsafe { std::slice::from_raw_parts(unterminated_text as *const u8, len) };
    let utf8 = unsafe { str::from_utf8_unchecked(bytes) };

    xml.characters(utf8);
}
<commit_msg>(#377): xml.rs: ignore character data outside the toplevel XML element<commit_after>use encoding::label::encoding_from_whatwg_label;
use encoding::DecoderTrap;
use libc;
use std;
use std::mem;
use std::ptr;
use std::rc::Rc;
use std::str;

use attributes::Attribute;
use css;
use handle::{self, RsvgHandle};
use load::rsvg_load_new_node;
use node::{node_new, Node, NodeType};
use property_bag::PropertyBag;
use structure::NodeSvg;
use style::NodeStyle;
use text::NodeChars;
use tree::{RsvgTree, Tree};
use util::utf8_cstr;

enum ContextKind {
    // Starting state
    Start,

    // Creating nodes for elements under the current node
    ElementCreation,

    // Inside <xi:include>
    XInclude(XIncludeContext),

    // An unsupported element inside a <xi:include> context, to be ignored
    UnsupportedXIncludeChild,

    // Insie <xi::fallback>
    XIncludeFallback(XIncludeContext),
}

#[derive(Clone)]
struct XIncludeContext {
    need_fallback: bool,
}

/// A concrete parsing context for a surrounding `element_name` and its XML event handlers
struct Context {
    element_name: String,
    kind: ContextKind,
}

impl Context {
    fn empty() -> Context {
        Context {
            element_name: String::new(),
            kind: ContextKind::Start,
        }
    }
}

// A *const RsvgXmlState is just the type that we export to C
pub enum RsvgXmlState {}

/// Holds the state used for XML processing
///
/// These methods are called when an XML event is parsed out of the XML stream: `start_element`,
/// `end_element`, `characters`.
///
/// When an element starts, we push a corresponding `Context` into the `context_stack`.  Within
/// that context, all XML events will be forwarded to it, and processed in one of the `XmlHandler`
/// trait objects. Normally the context refers to a `NodeCreationContext` implementation which is
/// what creates normal graphical elements.
struct XmlState {
    tree: Option<Box<Tree>>,
    context: Context,
    context_stack: Vec<Context>,
    current_node: Option<Rc<Node>>,
}

impl XmlState {
    fn new() -> XmlState {
        XmlState {
            tree: None,
            context: Context::empty(),
            context_stack: Vec::new(),
            current_node: None,
        }
    }

    pub fn set_root(&mut self, root: &Rc<Node>) {
        if self.tree.is_some() {
            panic!("The tree root has already been set");
        }

        self.tree = Some(Box::new(Tree::new(root)));
    }

    pub fn steal_tree(&mut self) -> Option<Box<Tree>> {
        self.tree.take()
    }

    fn push_context(&mut self, ctx: Context) {
        let top = mem::replace(&mut self.context, ctx);
        self.context_stack.push(top);
    }

    pub fn start_element(&mut self, handle: *mut RsvgHandle, name: &str, pbag: &PropertyBag) {
        let ctx = mem::replace(&mut self.context, Context::empty());

        let new_ctx = match ctx.kind {
            ContextKind::Start => self.element_creation_start_element(handle, name, pbag),
            ContextKind::ElementCreation => self.element_creation_start_element(handle, name, pbag),
            ContextKind::XInclude(ref ctx) => self.inside_xinclude_start_element(ctx, name),
            ContextKind::UnsupportedXIncludeChild => self.unsupported_xinclude_start_element(name),
            ContextKind::XIncludeFallback(ref ctx) => {
                self.xinclude_fallback_start_element(&ctx.clone(), handle, name, pbag)
            }
        };

        mem::replace(&mut self.context, ctx);
        self.push_context(new_ctx);
    }

    pub fn end_element(&mut self, handle: *mut RsvgHandle, name: &str) {
        // We can unwrap since start_element() always adds a context to the stack
        let top = self.context_stack.pop().unwrap();

        let context = mem::replace(&mut self.context, top);

        assert!(context.element_name == name);

        match context.kind {
            ContextKind::Start => panic!("end_element: XML handler stack is empty!?"),
            ContextKind::ElementCreation => self.element_creation_end_element(handle),
            ContextKind::XInclude(_) => (),
            ContextKind::UnsupportedXIncludeChild => (),
            ContextKind::XIncludeFallback(_) => (),
        }
    }

    pub fn characters(&mut self, text: &str) {
        let ctx = mem::replace(&mut self.context, Context::empty());

        match ctx.kind {
            ContextKind::Start => (), // character data outside the toplevel element?  Ignore it.
            ContextKind::ElementCreation => self.element_creation_characters(text),
            ContextKind::XInclude(_) => (),
            ContextKind::UnsupportedXIncludeChild => (),
            ContextKind::XIncludeFallback(ref ctx) => {
                self.xinclude_fallback_characters(&ctx.clone(), text)
            }
        }

        mem::replace(&mut self.context, ctx);
    }

    fn element_creation_start_element(
        &mut self,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        match name {
            "include" => self.xinclude_start_element(handle, name, pbag),
            _ => {
                let node = self.create_node(self.current_node.as_ref(), handle, name, pbag);
                if self.current_node.is_none() {
                    self.set_root(&node);
                }
                self.current_node = Some(node);

                Context {
                    element_name: name.to_string(),
                    kind: ContextKind::ElementCreation,
                }
            }
        }
    }

    fn element_creation_end_element(&mut self, handle: *mut RsvgHandle) {
        let node = self.current_node.take().unwrap();

        // The "svg" node is special; it parses its style attributes
        // here, not during element creation.
        if node.get_type() == NodeType::Svg {
            node.with_impl(|svg: &NodeSvg| {
                svg.set_delayed_style(&node, handle);
            });
        }

        if node.get_type() == NodeType::Style {
            let css_data = node.with_impl(|style: &NodeStyle| style.get_css(&node));

            css::parse_into_handle(handle, &css_data);
        }

        self.current_node = node.get_parent();
    }

    fn element_creation_characters(&self, text: &str) {
        let node = self.current_node.as_ref().unwrap();

        if text.len() != 0 && node.accept_chars() {
            let chars_node = if let Some(child) = node.find_last_chars_child() {
                child
            } else {
                let child = node_new(
                    NodeType::Chars,
                    Some(node),
                    "rsvg-chars",
                    None,
                    None,
                    Box::new(NodeChars::new()),
                );
                node.add_child(&child);
                child
            };

            chars_node.with_impl(|chars: &NodeChars| {
                chars.append(text);
            });
        }
    }

    fn create_node(
        &self,
        parent: Option<&Rc<Node>>,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Rc<Node> {
        let mut defs = handle::get_defs(handle);

        let new_node = rsvg_load_new_node(name, parent, pbag, &mut defs);

        if let Some(parent) = parent {
            parent.add_child(&new_node);
        }

        new_node.set_atts(&new_node, handle, pbag);

        // The "svg" node is special; it will parse its style attributes
        // until the end, in standard_element_end().
        if new_node.get_type() != NodeType::Svg {
            new_node.set_style(handle, pbag);
        }

        new_node.set_overridden_properties();

        new_node
    }

    fn xinclude_start_element(
        &mut self,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        let mut href = None;
        let mut parse = None;
        let mut encoding = None;

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::Href => href = Some(value),
                Attribute::Parse => parse = Some(value),
                Attribute::Encoding => encoding = Some(value),
                _ => (),
            }
        }

        let need_fallback = !self.acquire(handle, href, parse, encoding).is_ok();

        Context {
            element_name: name.to_string(),
            kind: ContextKind::XInclude(XIncludeContext { need_fallback }),
        }
    }

    fn inside_xinclude_start_element(&self, ctx: &XIncludeContext, name: &str) -> Context {
        // FIXME: we aren't using the xi: namespace
        if name == "fallback" {
            Context {
                element_name: name.to_string(),
                kind: ContextKind::XIncludeFallback(ctx.clone()),
            }
        } else {
            self.unsupported_xinclude_start_element(name)
        }
    }

    fn xinclude_fallback_start_element(
        &mut self,
        ctx: &XIncludeContext,
        handle: *mut RsvgHandle,
        name: &str,
        pbag: &PropertyBag,
    ) -> Context {
        if ctx.need_fallback {
            // FIXME: we aren't using the xi: namespace
            if name == "include" {
                self.xinclude_start_element(handle, name, pbag)
            } else {
                self.element_creation_start_element(handle, name, pbag)
            }
        } else {
            Context {
                element_name: name.to_string(),
                kind: ContextKind::UnsupportedXIncludeChild,
            }
        }
    }

    fn xinclude_fallback_characters(&mut self, ctx: &XIncludeContext, text: &str) {
        if ctx.need_fallback {
            self.element_creation_characters(text);
        }
    }

    fn acquire(
        &mut self,
        handle: *mut RsvgHandle,
        href: Option<&str>,
        parse: Option<&str>,
        encoding: Option<&str>,
    ) -> Result<(), ()> {
        if let Some(href) = href {
            if parse == Some("text") {
                self.acquire_text(handle, href, encoding)
            } else {
                self.acquire_xml(handle, href)
            }
        } else {
            Err(())
        }
    }

    fn acquire_text(
        &mut self,
        handle: *mut RsvgHandle,
        href: &str,
        encoding: Option<&str>,
    ) -> Result<(), ()> {
        let binary = handle::acquire_data(handle, href).map_err(|e| {
            rsvg_log!("could not acquire \"{}\": {}", href, e);
            ()
        })?;

        let encoding = encoding.unwrap_or("utf-8");

        let encoder = encoding_from_whatwg_label(encoding).ok_or_else(|| {
            rsvg_log!("unknown encoding \"{}\" for \"{}\"", encoding, href);
            ()
        })?;

        let utf8_data = encoder
            .decode(&binary.data, DecoderTrap::Strict)
            .map_err(|e| {
                rsvg_log!(
                    "could not convert contents of \"{}\" from character encoding \"{}\": {}",
                    href,
                    encoding,
                    e
                );
                ()
            })?;

        self.element_creation_characters(&utf8_data);
        Ok(())
    }

    fn acquire_xml(&self, handle: *mut RsvgHandle, href: &str) -> Result<(), ()> {
        if handle::load_xml_xinclude(handle, href) {
            Ok(())
        } else {
            Err(())
        }
    }

    fn unsupported_xinclude_start_element(&self, name: &str) -> Context {
        Context {
            element_name: name.to_string(),
            kind: ContextKind::UnsupportedXIncludeChild,
        }
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_new() -> *mut RsvgXmlState {
    Box::into_raw(Box::new(XmlState::new())) as *mut RsvgXmlState
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_free(xml: *mut RsvgXmlState) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };
    unsafe {
        Box::from_raw(xml);
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_steal_tree(xml: *mut RsvgXmlState) -> *mut RsvgTree {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    if let Some(tree) = xml.steal_tree() {
        Box::into_raw(tree) as *mut RsvgTree
    } else {
        ptr::null_mut()
    }
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_start_element(
    xml: *mut RsvgXmlState,
    handle: *mut RsvgHandle,
    name: *const libc::c_char,
    pbag: *const PropertyBag,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!name.is_null());
    let name = unsafe { utf8_cstr(name) };

    assert!(!pbag.is_null());
    let pbag = unsafe { &*pbag };

    xml.start_element(handle, name, pbag);
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_end_element(
    xml: *mut RsvgXmlState,
    handle: *mut RsvgHandle,
    name: *const libc::c_char,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!name.is_null());
    let name = unsafe { utf8_cstr(name) };

    xml.end_element(handle, name);
}

#[no_mangle]
pub extern "C" fn rsvg_xml_state_characters(
    xml: *mut RsvgXmlState,
    unterminated_text: *const libc::c_char,
    len: usize,
) {
    assert!(!xml.is_null());
    let xml = unsafe { &mut *(xml as *mut XmlState) };

    assert!(!unterminated_text.is_null());

    // libxml2 already validated the incoming string as UTF-8.  Note that
    // it is *not* nul-terminated; this is why we create a byte slice first.
    let bytes = unsafe { std::slice::from_raw_parts(unterminated_text as *const u8, len) };
    let utf8 = unsafe { str::from_utf8_unchecked(bytes) };

    xml.characters(utf8);
}
<|endoftext|>


<commit_before>use snafu::{Backtrace, Snafu};
use std::io;
use std::path;
use std::result;
use std::string;

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
    #[snafu(display("file io error [{:?}]: {}", path, source))]
    FileIO {
        path: path::PathBuf,
        source: io::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("dir entry error [{:?}]: {}", dir, source))]
    EntryIO {
        dir: path::PathBuf,
        source: io::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("base64 decode error: {}", source))]
    Base64Decode {
        source: base64::DecodeError,
        backtrace: Backtrace,
    },

    #[snafu(display("utf8 decode error: {}", source))]
    UTF8Decode {
        source: string::FromUtf8Error,
        backtrace: Backtrace,
    },

    #[snafu(display("json decode error: {}", source))]
    JsonDecode {
        source: serde_json::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("failed to create regex pattern \"{}\": {}", pattern, source))]
    Regex {
        pattern: String,
        source: regex::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("reqwest error: {}", source))]
    Reqwest {
        source: reqwest::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("failed to parse episode title: {}", name))]
    NoEpisodeTitle { name: String },

    #[snafu(display("expected episode number for file: {}", name))]
    ExpectedEpNumber { name: String },

    #[snafu(display("failed to parse episode: {}", name))]
    NoEpMatches { name: String },

    #[snafu(display(
        "found different episode titles:\n\texpecting: {}\n\tfound: {}",
        expecting,
        found
    ))]
    MultipleTitles { expecting: String, found: String },

    #[snafu(display("need existing series info to use offline backend"))]
    NeedExistingSeriesData,

    #[snafu(display("received bad response from AniList (code {}): {}", code, message))]
    BadAniListResponse { code: u16, message: String },

    #[snafu(display("missing group \"{}\" in custom episode matcher", group))]
    MissingCustomMatcherGroup { group: &'static str },

    #[snafu(display("must be authorized to make this request"))]
    NeedAuthentication,
}

impl Error {
    pub fn is_http_code(&self, http_code: u16) -> bool {
        match self {
            Error::BadAniListResponse { code, .. } if http_code == *code => true,
            Error::Reqwest { source, .. } => {
                let status = match source.status() {
                    Some(status) => status,
                    None => return false,
                };

                status.as_u16() == http_code
            }
            _ => false,
        }
    }
}
<commit_msg>anime: improve description for NoEpMatches error variant<commit_after>use snafu::{Backtrace, Snafu};
use std::io;
use std::path;
use std::result;
use std::string;

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
    #[snafu(display("file io error [{:?}]: {}", path, source))]
    FileIO {
        path: path::PathBuf,
        source: io::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("dir entry error [{:?}]: {}", dir, source))]
    EntryIO {
        dir: path::PathBuf,
        source: io::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("base64 decode error: {}", source))]
    Base64Decode {
        source: base64::DecodeError,
        backtrace: Backtrace,
    },

    #[snafu(display("utf8 decode error: {}", source))]
    UTF8Decode {
        source: string::FromUtf8Error,
        backtrace: Backtrace,
    },

    #[snafu(display("json decode error: {}", source))]
    JsonDecode {
        source: serde_json::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("failed to create regex pattern \"{}\": {}", pattern, source))]
    Regex {
        pattern: String,
        source: regex::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("reqwest error: {}", source))]
    Reqwest {
        source: reqwest::Error,
        backtrace: Backtrace,
    },

    #[snafu(display("failed to parse episode title: {}", name))]
    NoEpisodeTitle { name: String },

    #[snafu(display("expected episode number for file: {}", name))]
    ExpectedEpNumber { name: String },

    #[snafu(display("no title or episode found while parsing episode: {}", name))]
    NoEpMatches { name: String },

    #[snafu(display(
        "found different episode titles:\n\texpecting: {}\n\tfound: {}",
        expecting,
        found
    ))]
    MultipleTitles { expecting: String, found: String },

    #[snafu(display("need existing series info to use offline backend"))]
    NeedExistingSeriesData,

    #[snafu(display("received bad response from AniList (code {}): {}", code, message))]
    BadAniListResponse { code: u16, message: String },

    #[snafu(display("missing group \"{}\" in custom episode matcher", group))]
    MissingCustomMatcherGroup { group: &'static str },

    #[snafu(display("must be authorized to make this request"))]
    NeedAuthentication,
}

impl Error {
    pub fn is_http_code(&self, http_code: u16) -> bool {
        match self {
            Error::BadAniListResponse { code, .. } if http_code == *code => true,
            Error::Reqwest { source, .. } => {
                let status = match source.status() {
                    Some(status) => status,
                    None => return false,
                };

                status.as_u16() == http_code
            }
            _ => false,
        }
    }
}
<|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.
mod topologic_sort;

use hir::db::DefDatabase;
use ide_db::base_db::{
    salsa::{Database, ParallelDatabase, Snapshot},
    CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt,
};
use rustc_hash::{FxHashMap, 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();
    let to_prime = compute_crates_to_prime(db, &graph);

    let n_total = to_prime.len();
    for (n_done, &crate_id) in to_prime.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, n_total });
        // This also computes the DefMap
        db.import_map(crate_id);
    }
}

/// We're indexing many crates.
#[derive(Debug)]
pub struct ParallelPrimeCachesProgress {
    /// the crates that we are currently priming.
    pub crates_currently_indexing: Vec<String>,
    /// the total number of crates we want to prime.
    pub crates_total: usize,
    /// the total number of crates that have finished priming
    pub crates_done: usize,
}

pub(crate) fn parallel_prime_caches<F>(db: &RootDatabase, num_worker_threads: u8, cb: &F)
where
    F: Fn(ParallelPrimeCachesProgress) + Sync + std::panic::UnwindSafe,
{
    let _p = profile::span("prime_caches");

    let graph = db.crate_graph();
    let mut crates_to_prime = {
        let crate_ids = compute_crates_to_prime(db, &graph);

        let mut builder = topologic_sort::TopologicalSortIter::builder();

        for &crate_id in &crate_ids {
            let crate_data = &graph[crate_id];
            let dependencies = crate_data
                .dependencies
                .iter()
                .map(|d| d.crate_id)
                .filter(|i| crate_ids.contains(i));

            builder.add(crate_id, dependencies);
        }

        builder.build()
    };

    crossbeam_utils::thread::scope(move |s| {
        let (work_sender, work_receiver) = crossbeam_channel::unbounded();
        let (progress_sender, progress_receiver) = crossbeam_channel::unbounded();

        enum ParallelPrimeCacheWorkerProgress {
            BeginCrate { crate_id: CrateId, crate_name: String },
            EndCrate { crate_id: CrateId },
        }

        let prime_caches_worker = move |db: Snapshot<RootDatabase>| {
            while let Ok((crate_id, crate_name)) = work_receiver.recv() {
                progress_sender
                    .send(ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name })?;

                // This also computes the DefMap
                db.import_map(crate_id);

                progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrate { crate_id })?;
            }

            Ok::<_, crossbeam_channel::SendError<_>>(())
        };

        for _ in 0..num_worker_threads {
            let worker = prime_caches_worker.clone();
            let db = db.snapshot();
            s.spawn(move |_| worker(db));
        }

        let crates_total = crates_to_prime.len();
        let mut crates_done = 0;
        let mut crates_currently_indexing =
            FxHashMap::with_capacity_and_hasher(num_worker_threads as _, Default::default());

        while !crates_to_prime.is_empty() {
            db.unwind_if_cancelled();

            for crate_id in &mut crates_to_prime {
                work_sender
                    .send((
                        crate_id,
                        graph[crate_id].display_name.as_deref().unwrap_or_default().to_string(),
                    ))
                    .ok();
            }

            let worker_progress = match progress_receiver.recv() {
                Ok(p) => p,
                Err(_) => {
                    // our workers may have died from a cancelled task, so we'll check and re-raise here.
                    db.unwind_if_cancelled();
                    break;
                }
            };
            match worker_progress {
                ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name } => {
                    crates_currently_indexing.insert(crate_id, crate_name);
                }
                ParallelPrimeCacheWorkerProgress::EndCrate { crate_id } => {
                    crates_currently_indexing.remove(&crate_id);
                    crates_to_prime.mark_done(crate_id);
                    crates_done += 1;
                }
            };

            let progress = ParallelPrimeCachesProgress {
                crates_currently_indexing: crates_currently_indexing.values().cloned().collect(),
                crates_done,
                crates_total,
            };

            cb(progress);
        }
    })
    .unwrap();
}

fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> FxHashSet<CrateId> {
    // We're only interested in the workspace crates and the `ImportMap`s of their direct
    // dependencies, though in practice the latter also compute the `DefMap`s.
    // We don't prime transitive dependencies because they're generally not visible in
    // the current workspace.
    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[id].dependencies.iter().map(|krate| krate.crate_id))
        .collect()
}
<commit_msg>make progress callback dyn<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.
mod topologic_sort;

use hir::db::DefDatabase;
use ide_db::base_db::{
    salsa::{Database, ParallelDatabase, Snapshot},
    CrateGraph, CrateId, SourceDatabase, SourceDatabaseExt,
};
use rustc_hash::{FxHashMap, 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();
    let to_prime = compute_crates_to_prime(db, &graph);

    let n_total = to_prime.len();
    for (n_done, &crate_id) in to_prime.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, n_total });
        // This also computes the DefMap
        db.import_map(crate_id);
    }
}

/// We're indexing many crates.
#[derive(Debug)]
pub struct ParallelPrimeCachesProgress {
    /// the crates that we are currently priming.
    pub crates_currently_indexing: Vec<String>,
    /// the total number of crates we want to prime.
    pub crates_total: usize,
    /// the total number of crates that have finished priming
    pub crates_done: usize,
}

pub(crate) fn parallel_prime_caches(
    db: &RootDatabase,
    num_worker_threads: u8,
    cb: &(dyn Fn(ParallelPrimeCachesProgress) + Sync),
) {
    let _p = profile::span("prime_caches");

    let graph = db.crate_graph();
    let mut crates_to_prime = {
        let crate_ids = compute_crates_to_prime(db, &graph);

        let mut builder = topologic_sort::TopologicalSortIter::builder();

        for &crate_id in &crate_ids {
            let crate_data = &graph[crate_id];
            let dependencies = crate_data
                .dependencies
                .iter()
                .map(|d| d.crate_id)
                .filter(|i| crate_ids.contains(i));

            builder.add(crate_id, dependencies);
        }

        builder.build()
    };

    crossbeam_utils::thread::scope(move |s| {
        let (work_sender, work_receiver) = crossbeam_channel::unbounded();
        let (progress_sender, progress_receiver) = crossbeam_channel::unbounded();

        enum ParallelPrimeCacheWorkerProgress {
            BeginCrate { crate_id: CrateId, crate_name: String },
            EndCrate { crate_id: CrateId },
        }

        let prime_caches_worker = move |db: Snapshot<RootDatabase>| {
            while let Ok((crate_id, crate_name)) = work_receiver.recv() {
                progress_sender
                    .send(ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name })?;

                // This also computes the DefMap
                db.import_map(crate_id);

                progress_sender.send(ParallelPrimeCacheWorkerProgress::EndCrate { crate_id })?;
            }

            Ok::<_, crossbeam_channel::SendError<_>>(())
        };

        for _ in 0..num_worker_threads {
            let worker = prime_caches_worker.clone();
            let db = db.snapshot();
            s.spawn(move |_| worker(db));
        }

        let crates_total = crates_to_prime.len();
        let mut crates_done = 0;
        let mut crates_currently_indexing =
            FxHashMap::with_capacity_and_hasher(num_worker_threads as _, Default::default());

        while !crates_to_prime.is_empty() {
            db.unwind_if_cancelled();

            for crate_id in &mut crates_to_prime {
                work_sender
                    .send((
                        crate_id,
                        graph[crate_id].display_name.as_deref().unwrap_or_default().to_string(),
                    ))
                    .ok();
            }

            let worker_progress = match progress_receiver.recv() {
                Ok(p) => p,
                Err(_) => {
                    // our workers may have died from a cancelled task, so we'll check and re-raise here.
                    db.unwind_if_cancelled();
                    break;
                }
            };
            match worker_progress {
                ParallelPrimeCacheWorkerProgress::BeginCrate { crate_id, crate_name } => {
                    crates_currently_indexing.insert(crate_id, crate_name);
                }
                ParallelPrimeCacheWorkerProgress::EndCrate { crate_id } => {
                    crates_currently_indexing.remove(&crate_id);
                    crates_to_prime.mark_done(crate_id);
                    crates_done += 1;
                }
            };

            let progress = ParallelPrimeCachesProgress {
                crates_currently_indexing: crates_currently_indexing.values().cloned().collect(),
                crates_done,
                crates_total,
            };

            cb(progress);
        }
    })
    .unwrap();
}

fn compute_crates_to_prime(db: &RootDatabase, graph: &CrateGraph) -> FxHashSet<CrateId> {
    // We're only interested in the workspace crates and the `ImportMap`s of their direct
    // dependencies, though in practice the latter also compute the `DefMap`s.
    // We don't prime transitive dependencies because they're generally not visible in
    // the current workspace.
    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[id].dependencies.iter().map(|krate| krate.crate_id))
        .collect()
}
<|endoftext|>


<commit_before>//! # Span
//!
//! `Span`s represent a single operation within a trace. `Span`s can be nested to form a trace
//! tree. Each trace contains a root span, which typically describes the end-to-end latency and,
//! optionally, one or more sub-spans for its sub-operations.
//!
//! The `Span`'s start and end timestamps reflect the elapsed real time of the operation. A `Span`'s
//! start time is set to the current time on span creation. After the `Span` is created, it
//! is possible to change its name, set its `Attributes`, and add `Links` and `Events`.
//! These cannot be changed after the `Span`'s end time has been set.
use crate::trace::{Event, SpanContext, SpanId, SpanKind, StatusCode};
use crate::{api, exporter, sdk, KeyValue};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;

/// Single operation within a trace.
#[derive(Clone, Debug)]
pub struct Span {
    inner: Arc<SpanInner>,
}

/// Inner data, processed and exported on drop
#[derive(Debug)]
struct SpanInner {
    span_context: SpanContext,
    data: Option<Mutex<Option<SpanData>>>,
    tracer: sdk::trace::Tracer,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct SpanData {
    /// Span parent id
    pub(crate) parent_span_id: SpanId,
    /// Span kind
    pub(crate) span_kind: SpanKind,
    /// Span name
    pub(crate) name: String,
    /// Span start time
    pub(crate) start_time: SystemTime,
    /// Span end time
    pub(crate) end_time: SystemTime,
    /// Span attributes
    pub(crate) attributes: sdk::trace::EvictedHashMap,
    /// Span Message events
    pub(crate) message_events: sdk::trace::EvictedQueue<api::trace::Event>,
    /// Span Links
    pub(crate) links: sdk::trace::EvictedQueue<api::trace::Link>,
    /// Span status code
    pub(crate) status_code: StatusCode,
    /// Span status message
    pub(crate) status_message: String,
    /// Resource contains attributes representing an entity that produced this span.
    pub(crate) resource: Arc<sdk::Resource>,
}

impl Span {
    pub(crate) fn new(
        span_context: SpanContext,
        data: Option<SpanData>,
        tracer: sdk::trace::Tracer,
    ) -> Self {
        Span {
            inner: Arc::new(SpanInner {
                span_context,
                data: data.map(|data| Mutex::new(Some(data))),
                tracer,
            }),
        }
    }

    /// Operate on a mutable reference to span data
    fn with_data<T, F>(&self, f: F) -> Option<T>
    where
        F: FnOnce(&mut SpanData) -> T,
    {
        self.inner.data.as_ref().and_then(|inner| {
            inner
                .lock()
                .ok()
                .and_then(|mut span_data| span_data.as_mut().map(f))
        })
    }
}

impl crate::trace::Span for Span {
    /// Records events at a specific time in the context of a given `Span`.
    ///
    /// Note that the OpenTelemetry project documents certain ["standard event names and
    /// keys"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
    /// which have prescribed semantic meanings.
    fn add_event_with_timestamp(
        &self,
        name: String,
        timestamp: SystemTime,
        attributes: Vec<KeyValue>,
    ) {
        self.with_data(|data| {
            data.message_events
                .push_back(Event::new(name, timestamp, attributes))
        });
    }

    /// Returns the `SpanContext` for the given `Span`.
    fn span_context(&self) -> &SpanContext {
        &self.inner.span_context
    }

    /// Returns true if this `Span` is recording information like events with the `add_event`
    /// operation, attributes using `set_attributes`, status with `set_status`, etc.
    fn is_recording(&self) -> bool {
        self.inner.data.is_some()
    }

    /// Sets a single `Attribute` where the attribute properties are passed as arguments.
    ///
    /// Note that the OpenTelemetry project documents certain ["standard
    /// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
    /// that have prescribed semantic meanings.
    fn set_attribute(&self, attribute: KeyValue) {
        self.with_data(|data| {
            data.attributes.insert(attribute);
        });
    }

    /// Sets the status of the `Span`. If used, this will override the default `Span`
    /// status, which is `Unset`.
    fn set_status(&self, code: StatusCode, message: String) {
        self.with_data(|data| {
            data.status_code = code;
            data.status_message = message
        });
    }

    /// Updates the `Span`'s name.
    fn update_name(&self, new_name: String) {
        self.with_data(|data| {
            data.name = new_name;
        });
    }

    /// Finishes the span with given timestamp.
    fn end_with_timestamp(&self, timestamp: SystemTime) {
        self.with_data(|data| {
            data.end_time = timestamp;
        });
    }
}

impl Drop for SpanInner {
    /// Report span on inner drop
    fn drop(&mut self) {
        if let Some(data) = self.data.take() {
            if let Ok(mut span_data) = data.lock().map(|mut data| data.take()) {
                if let Some(provider) = self.tracer.provider() {
                    // Set end time if unset or invalid
                    if let Some(data) = span_data.as_mut() {
                        if data.end_time <= data.start_time {
                            data.end_time = SystemTime::now();
                        }
                    }
                    let mut processors = provider.span_processors().iter().peekable();
                    while let Some(processor) = processors.next() {
                        let span_data = if processors.peek().is_none() {
                            // last loop or single processor/exporter, move data
                            span_data.take()
                        } else {
                            // clone so each exporter gets owned data
                            span_data.clone()
                        };

                        if let Some(span_data) = span_data {
                            processor.on_end(build_export_data(
                                span_data,
                                self.span_context.clone(),
                                &self.tracer,
                            ));
                        }
                    }
                }
            }
        }
    }
}

fn build_export_data(
    data: SpanData,
    span_context: SpanContext,
    tracer: &sdk::trace::Tracer,
) -> exporter::trace::SpanData {
    exporter::trace::SpanData {
        span_context,
        parent_span_id: data.parent_span_id,
        span_kind: data.span_kind,
        name: data.name,
        start_time: data.start_time,
        end_time: data.end_time,
        attributes: data.attributes,
        message_events: data.message_events,
        links: data.links,
        status_code: data.status_code,
        status_message: data.status_message,
        resource: data.resource,
        instrumentation_lib: *tracer.instrumentation_library(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{api, api::core::KeyValue, api::trace::Span as _, api::trace::TracerProvider};
    use std::time::Duration;

    fn init() -> (sdk::trace::Tracer, SpanData) {
        let provider = sdk::trace::TracerProvider::default();
        let config = provider.config();
        let tracer = provider.get_tracer("opentelemetry", Some(env!("CARGO_PKG_VERSION")));
        let data = SpanData {
            parent_span_id: SpanId::from_u64(0),
            span_kind: api::trace::SpanKind::Internal,
            name: "opentelemetry".to_string(),
            start_time: SystemTime::now(),
            end_time: SystemTime::now(),
            attributes: sdk::trace::EvictedHashMap::new(config.max_attributes_per_span, 0),
            message_events: sdk::trace::EvictedQueue::new(config.max_events_per_span),
            links: sdk::trace::EvictedQueue::new(config.max_links_per_span),
            status_code: StatusCode::Unset,
            status_message: "".to_string(),
            resource: config.resource.clone(),
        };
        (tracer, data)
    }

    fn create_span() -> Span {
        let (tracer, data) = init();
        Span::new(SpanContext::empty_context(), Some(data), tracer)
    }

    #[test]
    fn create_span_without_data() {
        let (tracer, _) = init();
        let span = Span::new(SpanContext::empty_context(), None, tracer);
        span.with_data(|_data| panic!("there are data"));
    }

    #[test]
    fn create_span_with_data_mut() {
        let (tracer, data) = init();
        let span = Span::new(SpanContext::empty_context(), Some(data.clone()), tracer);
        span.with_data(|d| assert_eq!(*d, data));
    }

    #[test]
    fn add_event() {
        let span = create_span();
        let name = "some_event".to_string();
        let attributes = vec![KeyValue::new("k", "v")];
        span.add_event(name.clone(), attributes.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, name);
                assert_eq!(event.attributes, attributes);
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn add_event_with_timestamp() {
        let span = create_span();
        let name = "some_event".to_string();
        let attributes = vec![KeyValue::new("k", "v")];
        let timestamp = SystemTime::now();
        span.add_event_with_timestamp(name.clone(), timestamp, attributes.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.timestamp, timestamp);
                assert_eq!(event.name, name);
                assert_eq!(event.attributes, attributes);
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn record_exception() {
        let span = create_span();
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        span.record_exception(&err);
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, "exception");
                assert_eq!(
                    event.attributes,
                    vec![KeyValue::new("exception.message", err.to_string())]
                );
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn record_exception_with_stacktrace() {
        let span = create_span();
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        let stacktrace = "stacktrace...".to_string();
        span.record_exception_with_stacktrace(&err, stacktrace.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, "exception");
                assert_eq!(
                    event.attributes,
                    vec![
                        KeyValue::new("exception.message", err.to_string()),
                        KeyValue::new("exception.stacktrace", stacktrace),
                    ]
                );
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn set_attribute() {
        let span = create_span();
        let attributes = KeyValue::new("k", "v");
        span.set_attribute(attributes.clone());
        span.with_data(|data| {
            if let Some(val) = data.attributes.get(&attributes.key) {
                assert_eq!(*val, attributes.value);
            } else {
                panic!("no attribute");
            }
        });
    }

    #[test]
    fn set_status() {
        let span = create_span();
        let status = StatusCode::Ok;
        let message = "OK".to_string();
        span.set_status(status.clone(), message.clone());
        span.with_data(|data| {
            assert_eq!(data.status_code, status);
            assert_eq!(data.status_message, message);
        });
    }

    #[test]
    fn update_name() {
        let span = create_span();
        let name = "new_name".to_string();
        span.update_name(name.clone());
        span.with_data(|data| {
            assert_eq!(data.name, name);
        });
    }

    #[test]
    fn end() {
        let span = create_span();
        span.end();
    }

    #[test]
    fn end_with_timestamp() {
        let span = create_span();
        let timestamp = SystemTime::now();
        span.end_with_timestamp(timestamp);
        span.with_data(|data| assert_eq!(data.end_time, timestamp));
    }

    #[test]
    #[ignore = "not yet implemented"]
    fn end_only_once() {
        let span = create_span();
        let timestamp = SystemTime::now();
        span.end_with_timestamp(timestamp);
        span.end_with_timestamp(timestamp.checked_add(Duration::from_secs(10)).unwrap());
        span.with_data(|data| assert_eq!(data.end_time, timestamp));
    }

    #[test]
    #[ignore = "not yet implemented"]
    fn noop_after_end() {
        let span = create_span();
        let initial = span.with_data(|data| data.clone()).unwrap();
        span.end();
        span.add_event("some_event".to_string(), vec![KeyValue::new("k", "v")]);
        span.add_event_with_timestamp(
            "some_event".to_string(),
            SystemTime::now(),
            vec![KeyValue::new("k", "v")],
        );
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        span.record_exception(&err);
        span.record_exception_with_stacktrace(&err, "stacktrace...".to_string());
        span.set_attribute(KeyValue::new("k", "v"));
        span.set_status(StatusCode::Error, "ERROR".to_string());
        span.update_name("new_name".to_string());
        span.with_data(|data| {
            assert_eq!(data.message_events, initial.message_events);
            assert_eq!(data.attributes, initial.attributes);
            assert_eq!(data.status_code, initial.status_code);
            assert_eq!(data.status_message, initial.status_message);
            assert_eq!(data.name, initial.name);
        });
    }

    #[test]
    fn is_recording_true_when_not_ended() {
        let span = create_span();
        assert!(span.is_recording());
    }

    #[test]
    #[ignore = "not yet implemented"]
    fn is_recording_false_after_end() {
        let span = create_span();
        span.end();
        assert!(!span.is_recording());
    }
}
<commit_msg>Ensure `is_recording` is false and all methods are no-ops after span ends (#341)<commit_after>//! # Span
//!
//! `Span`s represent a single operation within a trace. `Span`s can be nested to form a trace
//! tree. Each trace contains a root span, which typically describes the end-to-end latency and,
//! optionally, one or more sub-spans for its sub-operations.
//!
//! The `Span`'s start and end timestamps reflect the elapsed real time of the operation. A `Span`'s
//! start time is set to the current time on span creation. After the `Span` is created, it
//! is possible to change its name, set its `Attributes`, and add `Links` and `Events`.
//! These cannot be changed after the `Span`'s end time has been set.
use crate::trace::{Event, SpanContext, SpanId, SpanKind, StatusCode};
use crate::{api, exporter, sdk, KeyValue};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;

/// Single operation within a trace.
#[derive(Clone, Debug)]
pub struct Span {
    inner: Arc<SpanInner>,
}

/// Inner data, processed and exported on end
#[derive(Debug)]
struct SpanInner {
    span_context: SpanContext,
    data: Option<Mutex<Option<SpanData>>>,
    tracer: sdk::trace::Tracer,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct SpanData {
    /// Span parent id
    pub(crate) parent_span_id: SpanId,
    /// Span kind
    pub(crate) span_kind: SpanKind,
    /// Span name
    pub(crate) name: String,
    /// Span start time
    pub(crate) start_time: SystemTime,
    /// Span end time
    pub(crate) end_time: SystemTime,
    /// Span attributes
    pub(crate) attributes: sdk::trace::EvictedHashMap,
    /// Span Message events
    pub(crate) message_events: sdk::trace::EvictedQueue<api::trace::Event>,
    /// Span Links
    pub(crate) links: sdk::trace::EvictedQueue<api::trace::Link>,
    /// Span status code
    pub(crate) status_code: StatusCode,
    /// Span status message
    pub(crate) status_message: String,
    /// Resource contains attributes representing an entity that produced this span.
    pub(crate) resource: Arc<sdk::Resource>,
}

impl Span {
    pub(crate) fn new(
        span_context: SpanContext,
        data: Option<SpanData>,
        tracer: sdk::trace::Tracer,
    ) -> Self {
        Span {
            inner: Arc::new(SpanInner {
                span_context,
                data: data.map(|data| Mutex::new(Some(data))),
                tracer,
            }),
        }
    }

    /// Operate on a mutable reference to span data
    fn with_data<T, F>(&self, f: F) -> Option<T>
    where
        F: FnOnce(&mut SpanData) -> T,
    {
        self.inner.data.as_ref().and_then(|inner| {
            inner
                .lock()
                .ok()
                .and_then(|mut span_data| span_data.as_mut().map(f))
        })
    }
}

impl crate::trace::Span for Span {
    /// Records events at a specific time in the context of a given `Span`.
    ///
    /// Note that the OpenTelemetry project documents certain ["standard event names and
    /// keys"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
    /// which have prescribed semantic meanings.
    fn add_event_with_timestamp(
        &self,
        name: String,
        timestamp: SystemTime,
        attributes: Vec<KeyValue>,
    ) {
        self.with_data(|data| {
            data.message_events
                .push_back(Event::new(name, timestamp, attributes))
        });
    }

    /// Returns the `SpanContext` for the given `Span`.
    fn span_context(&self) -> &SpanContext {
        &self.inner.span_context
    }

    /// Returns true if this `Span` is recording information like events with the `add_event`
    /// operation, attributes using `set_attributes`, status with `set_status`, etc.
    /// Always returns false after span `end`.
    fn is_recording(&self) -> bool {
        if let Some(data) = &self.inner.data {
            if let Ok(span_data) = data.lock() {
                return span_data.is_some();
            }
        }
        false
    }

    /// Sets a single `Attribute` where the attribute properties are passed as arguments.
    ///
    /// Note that the OpenTelemetry project documents certain ["standard
    /// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
    /// that have prescribed semantic meanings.
    fn set_attribute(&self, attribute: KeyValue) {
        self.with_data(|data| {
            data.attributes.insert(attribute);
        });
    }

    /// Sets the status of the `Span`. If used, this will override the default `Span`
    /// status, which is `Unset`.
    fn set_status(&self, code: StatusCode, message: String) {
        self.with_data(|data| {
            data.status_code = code;
            data.status_message = message
        });
    }

    /// Updates the `Span`'s name.
    fn update_name(&self, new_name: String) {
        self.with_data(|data| {
            data.name = new_name;
        });
    }

    /// Finishes the span with given timestamp.
    fn end_with_timestamp(&self, timestamp: SystemTime) {
        self.inner.ensure_ended_and_exported(Some(timestamp));
    }
}

impl SpanInner {
    fn ensure_ended_and_exported(&self, timestamp: Option<SystemTime>) {
        if let Some(data) = &self.data {
            if let Ok(mut span_data) = data.lock().map(|mut data| data.take()) {
                // Ensure end time is set via explicit end or implicitly on drop
                if let Some(span_data) = span_data.as_mut() {
                    if let Some(timestamp) = timestamp {
                        span_data.end_time = timestamp;
                    } else if span_data.end_time == span_data.start_time {
                        span_data.end_time = SystemTime::now();
                    }
                }

                // Notify each span processor that the span has ended
                if let Some(provider) = self.tracer.provider() {
                    let mut processors = provider.span_processors().iter().peekable();
                    while let Some(processor) = processors.next() {
                        let span_data = if processors.peek().is_none() {
                            // last loop or single processor/exporter, move data
                            span_data.take()
                        } else {
                            // clone so each exporter gets owned data
                            span_data.clone()
                        };

                        if let Some(span_data) = span_data {
                            processor.on_end(build_export_data(
                                span_data,
                                self.span_context.clone(),
                                &self.tracer,
                            ));
                        }
                    }
                }
            }
        }
    }
}

impl Drop for SpanInner {
    /// Report span on inner drop
    fn drop(&mut self) {
        self.ensure_ended_and_exported(None);
    }
}

fn build_export_data(
    data: SpanData,
    span_context: SpanContext,
    tracer: &sdk::trace::Tracer,
) -> exporter::trace::SpanData {
    exporter::trace::SpanData {
        span_context,
        parent_span_id: data.parent_span_id,
        span_kind: data.span_kind,
        name: data.name,
        start_time: data.start_time,
        end_time: data.end_time,
        attributes: data.attributes,
        message_events: data.message_events,
        links: data.links,
        status_code: data.status_code,
        status_message: data.status_message,
        resource: data.resource,
        instrumentation_lib: *tracer.instrumentation_library(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{api, api::core::KeyValue, api::trace::Span as _, api::trace::TracerProvider};
    use std::time::Duration;

    fn init() -> (sdk::trace::Tracer, SpanData) {
        let provider = sdk::trace::TracerProvider::default();
        let config = provider.config();
        let tracer = provider.get_tracer("opentelemetry", Some(env!("CARGO_PKG_VERSION")));
        let data = SpanData {
            parent_span_id: SpanId::from_u64(0),
            span_kind: api::trace::SpanKind::Internal,
            name: "opentelemetry".to_string(),
            start_time: SystemTime::now(),
            end_time: SystemTime::now(),
            attributes: sdk::trace::EvictedHashMap::new(config.max_attributes_per_span, 0),
            message_events: sdk::trace::EvictedQueue::new(config.max_events_per_span),
            links: sdk::trace::EvictedQueue::new(config.max_links_per_span),
            status_code: StatusCode::Unset,
            status_message: "".to_string(),
            resource: config.resource.clone(),
        };
        (tracer, data)
    }

    fn create_span() -> Span {
        let (tracer, data) = init();
        Span::new(SpanContext::empty_context(), Some(data), tracer)
    }

    #[test]
    fn create_span_without_data() {
        let (tracer, _) = init();
        let span = Span::new(SpanContext::empty_context(), None, tracer);
        span.with_data(|_data| panic!("there are data"));
    }

    #[test]
    fn create_span_with_data_mut() {
        let (tracer, data) = init();
        let span = Span::new(SpanContext::empty_context(), Some(data.clone()), tracer);
        span.with_data(|d| assert_eq!(*d, data));
    }

    #[test]
    fn add_event() {
        let span = create_span();
        let name = "some_event".to_string();
        let attributes = vec![KeyValue::new("k", "v")];
        span.add_event(name.clone(), attributes.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, name);
                assert_eq!(event.attributes, attributes);
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn add_event_with_timestamp() {
        let span = create_span();
        let name = "some_event".to_string();
        let attributes = vec![KeyValue::new("k", "v")];
        let timestamp = SystemTime::now();
        span.add_event_with_timestamp(name.clone(), timestamp, attributes.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.timestamp, timestamp);
                assert_eq!(event.name, name);
                assert_eq!(event.attributes, attributes);
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn record_exception() {
        let span = create_span();
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        span.record_exception(&err);
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, "exception");
                assert_eq!(
                    event.attributes,
                    vec![KeyValue::new("exception.message", err.to_string())]
                );
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn record_exception_with_stacktrace() {
        let span = create_span();
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        let stacktrace = "stacktrace...".to_string();
        span.record_exception_with_stacktrace(&err, stacktrace.clone());
        span.with_data(|data| {
            if let Some(event) = data.message_events.iter().next() {
                assert_eq!(event.name, "exception");
                assert_eq!(
                    event.attributes,
                    vec![
                        KeyValue::new("exception.message", err.to_string()),
                        KeyValue::new("exception.stacktrace", stacktrace),
                    ]
                );
            } else {
                panic!("no event");
            }
        });
    }

    #[test]
    fn set_attribute() {
        let span = create_span();
        let attributes = KeyValue::new("k", "v");
        span.set_attribute(attributes.clone());
        span.with_data(|data| {
            if let Some(val) = data.attributes.get(&attributes.key) {
                assert_eq!(*val, attributes.value);
            } else {
                panic!("no attribute");
            }
        });
    }

    #[test]
    fn set_status() {
        let span = create_span();
        let status = StatusCode::Ok;
        let message = "OK".to_string();
        span.set_status(status.clone(), message.clone());
        span.with_data(|data| {
            assert_eq!(data.status_code, status);
            assert_eq!(data.status_message, message);
        });
    }

    #[test]
    fn update_name() {
        let span = create_span();
        let name = "new_name".to_string();
        span.update_name(name.clone());
        span.with_data(|data| {
            assert_eq!(data.name, name);
        });
    }

    #[test]
    fn end() {
        let span = create_span();
        span.end();
    }

    #[test]
    fn end_with_timestamp() {
        let span = create_span();
        let timestamp = SystemTime::now();
        span.end_with_timestamp(timestamp);
        span.with_data(|data| assert_eq!(data.end_time, timestamp));
    }

    #[test]
    fn allows_to_get_span_context_after_end() {
        let span = create_span();
        span.end();
        assert_eq!(span.span_context(), &SpanContext::empty_context());
    }

    #[test]
    fn allows_to_get_span_context_after_clone_drop() {
        let span = create_span();
        drop(span.clone());
        assert_eq!(span.span_context(), &SpanContext::empty_context());
    }

    #[test]
    fn end_only_once() {
        let span = create_span();
        let timestamp = SystemTime::now();
        span.end_with_timestamp(timestamp);
        span.end_with_timestamp(timestamp.checked_add(Duration::from_secs(10)).unwrap());
        span.with_data(|data| assert_eq!(data.end_time, timestamp));
    }

    #[test]
    fn noop_after_end() {
        let span = create_span();
        let initial = span.with_data(|data| data.clone()).unwrap();
        span.end();
        span.add_event("some_event".to_string(), vec![KeyValue::new("k", "v")]);
        span.add_event_with_timestamp(
            "some_event".to_string(),
            SystemTime::now(),
            vec![KeyValue::new("k", "v")],
        );
        let err = std::io::Error::from(std::io::ErrorKind::Other);
        span.record_exception(&err);
        span.record_exception_with_stacktrace(&err, "stacktrace...".to_string());
        span.set_attribute(KeyValue::new("k", "v"));
        span.set_status(StatusCode::Error, "ERROR".to_string());
        span.update_name("new_name".to_string());
        span.with_data(|data| {
            assert_eq!(data.message_events, initial.message_events);
            assert_eq!(data.attributes, initial.attributes);
            assert_eq!(data.status_code, initial.status_code);
            assert_eq!(data.status_message, initial.status_message);
            assert_eq!(data.name, initial.name);
        });
    }

    #[test]
    fn is_recording_true_when_not_ended() {
        let span = create_span();
        assert!(span.is_recording());
    }

    #[test]
    fn is_recording_false_after_end() {
        let span = create_span();
        span.end();
        assert!(!span.is_recording());
    }
}
<|endoftext|>


<commit_before>//! `NameDefinition` keeps information about the element we want to search references for.
//! The element is represented by `NameKind`. It's located inside some `container` and
//! has a `visibility`, which defines a search scope.
//! Note that the reference search is possible for not all of the classified items.

use hir::{
    Adt, AssocItem, HasSource, ImplBlock, InFile, Local, MacroDef, Module, ModuleDef, SourceBinder,
    StructField, TypeParam, VariantDef,
};
use ra_prof::profile;
use ra_syntax::{
    ast::{self, AstNode, VisibilityOwner},
    match_ast,
};

use crate::RootDatabase;

#[derive(Debug, PartialEq, Eq)]
pub enum NameKind {
    Macro(MacroDef),
    Field(StructField),
    AssocItem(AssocItem),
    Def(ModuleDef),
    SelfType(ImplBlock),
    Local(Local),
    TypeParam(TypeParam),
}

#[derive(PartialEq, Eq)]
pub struct NameDefinition {
    pub visibility: Option<ast::Visibility>,
    /// FIXME: this doesn't really make sense. For example, builtin types don't
    /// really have a module.
    pub container: Module,
    pub kind: NameKind,
}

pub fn classify_name(
    sb: &mut SourceBinder<RootDatabase>,
    name: InFile<&ast::Name>,
) -> Option<NameDefinition> {
    let _p = profile("classify_name");
    let parent = name.value.syntax().parent()?;

    match_ast! {
        match parent {
            ast::BindPat(it) => {
                let src = name.with_value(it);
                let local = sb.to_def(src)?;
                Some(NameDefinition {
                    visibility: None,
                    container: local.module(sb.db),
                    kind: NameKind::Local(local),
                })
            },
            ast::RecordFieldDef(it) => {
                let src = name.with_value(it);
                let field: hir::StructField = sb.to_def(src)?;
                Some(from_struct_field(sb.db, field))
            },
            ast::Module(it) => {
                let def = sb.to_def(name.with_value(it))?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::StructDef(it) => {
                let src = name.with_value(it);
                let def: hir::Struct = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::EnumDef(it) => {
                let src = name.with_value(it);
                let def: hir::Enum = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::TraitDef(it) => {
                let src = name.with_value(it);
                let def: hir::Trait = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::StaticDef(it) => {
                let src = name.with_value(it);
                let def: hir::Static = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::EnumVariant(it) => {
                let src = name.with_value(it);
                let def: hir::EnumVariant = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::FnDef(it) => {
                let src = name.with_value(it);
                let def: hir::Function = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::ConstDef(it) => {
                let src = name.with_value(it);
                let def: hir::Const = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::TypeAliasDef(it) => {
                let src = name.with_value(it);
                let def: hir::TypeAlias = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::MacroCall(it) => {
                let src = name.with_value(it);
                let def = sb.to_def(src.clone())?;

                let module = sb.to_module_def(src.file_id.original_file(sb.db))?;

                Some(NameDefinition {
                    visibility: None,
                    container: module,
                    kind: NameKind::Macro(def),
                })
            },
            ast::TypeParam(it) => {
                let src = name.with_value(it);
                let def = sb.to_def(src)?;
                Some(NameDefinition {
                    visibility: None,
                    container: def.module(sb.db),
                    kind: NameKind::TypeParam(def),
                })
            },
            _ => None,
        }
    }
}

pub fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition {
    let container = item.module(db);
    let visibility = match item {
        AssocItem::Function(f) => f.source(db).value.visibility(),
        AssocItem::Const(c) => c.source(db).value.visibility(),
        AssocItem::TypeAlias(a) => a.source(db).value.visibility(),
    };
    let kind = NameKind::AssocItem(item);
    NameDefinition { kind, container, visibility }
}

pub fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition {
    let kind = NameKind::Field(field);
    let parent = field.parent_def(db);
    let container = parent.module(db);
    let visibility = match parent {
        VariantDef::Struct(s) => s.source(db).value.visibility(),
        VariantDef::Union(e) => e.source(db).value.visibility(),
        VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(),
    };
    NameDefinition { kind, container, visibility }
}

pub fn from_module_def(
    db: &RootDatabase,
    def: ModuleDef,
    module: Option<Module>,
) -> NameDefinition {
    let kind = NameKind::Def(def);
    let (container, visibility) = match def {
        ModuleDef::Module(it) => {
            let container = it.parent(db).or_else(|| Some(it)).unwrap();
            let visibility = it.declaration_source(db).and_then(|s| s.value.visibility());
            (container, visibility)
        }
        ModuleDef::EnumVariant(it) => {
            let container = it.module(db);
            let visibility = it.source(db).value.parent_enum().visibility();
            (container, visibility)
        }
        ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::BuiltinType(..) => (module.unwrap(), None),
    };
    NameDefinition { kind, container, visibility }
}
<commit_msg>Add a fixme note<commit_after>//! `NameDefinition` keeps information about the element we want to search references for.
//! The element is represented by `NameKind`. It's located inside some `container` and
//! has a `visibility`, which defines a search scope.
//! Note that the reference search is possible for not all of the classified items.

// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).

use hir::{
    Adt, AssocItem, HasSource, ImplBlock, InFile, Local, MacroDef, Module, ModuleDef, SourceBinder,
    StructField, TypeParam, VariantDef,
};
use ra_prof::profile;
use ra_syntax::{
    ast::{self, AstNode, VisibilityOwner},
    match_ast,
};

use crate::RootDatabase;

#[derive(Debug, PartialEq, Eq)]
pub enum NameKind {
    Macro(MacroDef),
    Field(StructField),
    AssocItem(AssocItem),
    Def(ModuleDef),
    SelfType(ImplBlock),
    Local(Local),
    TypeParam(TypeParam),
}

#[derive(PartialEq, Eq)]
pub struct NameDefinition {
    pub visibility: Option<ast::Visibility>,
    /// FIXME: this doesn't really make sense. For example, builtin types don't
    /// really have a module.
    pub container: Module,
    pub kind: NameKind,
}

pub fn classify_name(
    sb: &mut SourceBinder<RootDatabase>,
    name: InFile<&ast::Name>,
) -> Option<NameDefinition> {
    let _p = profile("classify_name");
    let parent = name.value.syntax().parent()?;

    match_ast! {
        match parent {
            ast::BindPat(it) => {
                let src = name.with_value(it);
                let local = sb.to_def(src)?;
                Some(NameDefinition {
                    visibility: None,
                    container: local.module(sb.db),
                    kind: NameKind::Local(local),
                })
            },
            ast::RecordFieldDef(it) => {
                let src = name.with_value(it);
                let field: hir::StructField = sb.to_def(src)?;
                Some(from_struct_field(sb.db, field))
            },
            ast::Module(it) => {
                let def = sb.to_def(name.with_value(it))?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::StructDef(it) => {
                let src = name.with_value(it);
                let def: hir::Struct = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::EnumDef(it) => {
                let src = name.with_value(it);
                let def: hir::Enum = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::TraitDef(it) => {
                let src = name.with_value(it);
                let def: hir::Trait = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::StaticDef(it) => {
                let src = name.with_value(it);
                let def: hir::Static = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::EnumVariant(it) => {
                let src = name.with_value(it);
                let def: hir::EnumVariant = sb.to_def(src)?;
                Some(from_module_def(sb.db, def.into(), None))
            },
            ast::FnDef(it) => {
                let src = name.with_value(it);
                let def: hir::Function = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::ConstDef(it) => {
                let src = name.with_value(it);
                let def: hir::Const = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::TypeAliasDef(it) => {
                let src = name.with_value(it);
                let def: hir::TypeAlias = sb.to_def(src)?;
                if parent.parent().and_then(ast::ItemList::cast).is_some() {
                    Some(from_assoc_item(sb.db, def.into()))
                } else {
                    Some(from_module_def(sb.db, def.into(), None))
                }
            },
            ast::MacroCall(it) => {
                let src = name.with_value(it);
                let def = sb.to_def(src.clone())?;

                let module = sb.to_module_def(src.file_id.original_file(sb.db))?;

                Some(NameDefinition {
                    visibility: None,
                    container: module,
                    kind: NameKind::Macro(def),
                })
            },
            ast::TypeParam(it) => {
                let src = name.with_value(it);
                let def = sb.to_def(src)?;
                Some(NameDefinition {
                    visibility: None,
                    container: def.module(sb.db),
                    kind: NameKind::TypeParam(def),
                })
            },
            _ => None,
        }
    }
}

pub fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition {
    let container = item.module(db);
    let visibility = match item {
        AssocItem::Function(f) => f.source(db).value.visibility(),
        AssocItem::Const(c) => c.source(db).value.visibility(),
        AssocItem::TypeAlias(a) => a.source(db).value.visibility(),
    };
    let kind = NameKind::AssocItem(item);
    NameDefinition { kind, container, visibility }
}

pub fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition {
    let kind = NameKind::Field(field);
    let parent = field.parent_def(db);
    let container = parent.module(db);
    let visibility = match parent {
        VariantDef::Struct(s) => s.source(db).value.visibility(),
        VariantDef::Union(e) => e.source(db).value.visibility(),
        VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(),
    };
    NameDefinition { kind, container, visibility }
}

pub fn from_module_def(
    db: &RootDatabase,
    def: ModuleDef,
    module: Option<Module>,
) -> NameDefinition {
    let kind = NameKind::Def(def);
    let (container, visibility) = match def {
        ModuleDef::Module(it) => {
            let container = it.parent(db).or_else(|| Some(it)).unwrap();
            let visibility = it.declaration_source(db).and_then(|s| s.value.visibility());
            (container, visibility)
        }
        ModuleDef::EnumVariant(it) => {
            let container = it.module(db);
            let visibility = it.source(db).value.parent_enum().visibility();
            (container, visibility)
        }
        ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()),
        ModuleDef::BuiltinType(..) => (module.unwrap(), None),
    };
    NameDefinition { kind, container, visibility }
}
<|endoftext|>