<commit_before>#[macro_use]
extern crate gluon_codegen;
extern crate gluon;
#[macro_use]
extern crate gluon_vm;

extern crate env_logger;
#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;
use std::sync::Arc;

use gluon::base::types::ArcType;

use gluon::base::types::{AppVec, Type};
use gluon::vm::api::generic::{A, L, R};
use gluon::vm::api::{
    self, ActiveThread, FunctionRef, Getable, Hole, OpaqueRef, OpaqueValue, Pushable,
    UserdataValue, ValueRef, VmType, IO,
};
use gluon::vm::{self, ExternModule, Variants};
use gluon::{import, new_vm, Compiler, Result, RootedThread, Thread};

#[derive(Debug, Deserialize, Serialize)]
enum Enum {
    A,
    B(i32),
    C(String, String),
}

impl api::VmType for Enum {
    type Type = Self;
    fn make_type(thread: &Thread) -> ArcType {
        thread
            .find_type_info("examples.enum.Enum")
            .unwrap()
            .clone()
            .into_type()
    }
}

impl<'vm, 'value> api::Pushable<'vm> for Enum {
    fn push(self, context: &mut ActiveThread<'vm>) -> vm::Result<()> {
        api::ser::Ser(self).push(context)
    }
}

impl<'vm, 'value> api::Getable<'vm, 'value> for Enum {
    impl_getable_simple!();
    fn from_value(thread: &'vm Thread, value: vm::Variants<'value>) -> Self {
        api::de::De::from_value(thread, value).0
    }
}

field_decl! { unwrap_b, value, key }

// we define Either with type parameters, just like in Gluon
#[derive(Getable, Pushable, VmType)]
#[gluon(vm_type = "examples.either.Either")]
enum Either<L, R> {
    Left(L),
    Right(R),
}

fn marshal_enum() -> Result<()> {
    let thread = new_vm();

    let enum_source = api::typ::make_source::<Enum>(&thread)?;
    Compiler::new().load_script(&thread, "examples.enum", &enum_source)?;

    let source = r#"
        let { Enum } = import! "examples/enum.glu"

        let unwrap_b x =
            match x with
            | B y -> y
            | _ -> error "Expected B"

        {
            unwrap_b,
            value = C "hello" "world"
        }
    "#;
    type SourceType<'thread> = record_type! {
        unwrap_b => api::FunctionRef<'thread, fn (Enum) -> i32>,
        value => Enum
    };
    let (record_p! { mut unwrap_b, value }, _) =
        Compiler::new().run_expr::<SourceType>(&thread, "example", source)?;
    match value {
        Enum::C(ref a, ref b) => {
            assert_eq!(a, "hello");
            assert_eq!(b, "world");
            println!("`value` evaluated to: {:?}", value)
        }
        _ => panic!("Unexpected result returned"),
    }

    let x = unwrap_b.call(Enum::B(123))?;
    assert_eq!(x, 123);
    println!("`unwrap_b` returned: {}", x);

    Ok(())
}

fn marshal_map<I>(iterable: I) -> Result<()>
where
    I: IntoIterator<Item = (String, String)>,
{
    let thread = new_vm();

    // Load std.map so that we can retrieve the `Map` type through the `VmType` trait
    Compiler::new().run_expr::<()>(&thread, "example", "let _ = import! std.map in ()")?;

    let config_example = r#"
        let array = import! std.array
        let map @ { Map } = import! std.map

        let run config_array =
            let f m entry : Map String String -> (String, String) -> _ =
                map.insert entry._0 entry._1 m
            array.foldable.foldl f map.empty config_array
        run
        "#;
    let mut make_map: FunctionRef<
        fn(Vec<(String, String)>) -> OpaqueValue<RootedThread, api::Map<String, String>>,
    > = Compiler::new()
        .run_expr(&thread, "example", config_example)?
        .0;

    let entries: Vec<_> = iterable.into_iter().collect();
    let map = make_map.call(entries)?;

    let config_query_example = r#"
        let map = import! std.map

        let run config_map =
            (map.find "key" config_map, map.find "undefined" config_map)
        run
        "#;
    let mut query_map: FunctionRef<
        fn(OpaqueValue<RootedThread, api::Map<String, String>>) -> (Option<String>, Option<String>),
    > = Compiler::new()
        .run_expr(&thread, "example", config_query_example)?
        .0;

    let tuple = query_map.call(map)?;
    assert_eq!(tuple, (Some("value".to_string()), None));
    println!("Querying the map returned: {:?}", tuple);

    Ok(())
}

// the function takes an Either instantiated with the `Opaque*` struct,
// which will handle the generic Gluon values for us.
//
// We use the `OpaqueRef` alias here since we only need temporary (borrowed) access and it avoids
// the cost of reference counted rooting.
fn flip<'a>(
    either: Either<OpaqueRef<'a, L>, OpaqueRef<'a, R>>,
) -> Either<OpaqueRef<'a, R>, OpaqueRef<'a, L>> {
    match either {
        Either::Left(val) => Either::Right(val),
        Either::Right(val) => Either::Left(val),
    }
}

fn marshal_generic() -> Result<()> {
    let vm = new_vm();
    let mut compiler = Compiler::new();

    // define the gluon type that maps to the rust Either
    let src = r#"
        type Either l r = | Left l | Right r
        { Either }
    "#;

    // load the type and then the module containing the rust function
    fn load_mod(vm: &Thread) -> vm::Result<ExternModule> {
        let module = record! {
            flip => primitive!(1, flip),
        };

        ExternModule::new(vm, module)
    }

    compiler.load_script(&vm, "examples.either", src).unwrap();
    import::add_extern_module(&vm, "examples.prim", load_mod);

    let script = r#"
        let { Either } = import! examples.either
        let { flip } = import! examples.prim
        let { (<>) } = import! std.semigroup
        let io @ { flat_map } = import! std.io

        // Either is defined as:
        // type Either l r = | Left l | Right r
        let either: forall r . Either String r = Left "hello rust!"

        // we can pass the generic Either to the Rust function without an issue
        seq
            match flip either with
            | Left _ -> error "unreachable!"
            | Right val -> io.println ("Right is: " <> val)

        // using an Int instead also works
        let either: forall r . Either Int r = Left 42

        match flip either with
        | Left _ -> error "also unreachable!"
        | Right 42 -> io.println "this is the right answer"
        | Right _ -> error "wrong answer!"
    "#;

    compiler
        .run_io(true)
        .run_expr::<IO<()>>(&vm, "example", script)?;

    Ok(())
}

// if this were a foreign type, we would not be able to
// directly implement traits for it
#[derive(Debug)]
struct User<T> {
    name: String,
    age: u32,
    data: T,
}

// but it's possible to wrap it and manually implement traits for it
struct GluonUser<T> {
    inner: User<T>,
}

impl<T> VmType for GluonUser<T>
where
    T: 'static + VmType,
{
    type Type = Self;

    fn make_type(vm: &Thread) -> ArcType {
        // get the type defined in Gluon
        let ty = vm
            .find_type_info("examples.wrapper.User")
            .expect("Could not find type")
            .into_type();

        // apply all generic parameters to the type
        let mut vec = AppVec::new();
        vec.push(T::make_type(vm));
        Type::app(ty, vec)
    }
}

impl<'vm, T> Pushable<'vm> for GluonUser<T>
where
    T: Pushable<'vm>,
{
    fn push(self, ctx: &mut ActiveThread<'vm>) -> vm::Result<()> {
        (record! {
            name => self.inner.name,
            age => self.inner.age,
            data => self.inner.data,
        })
        .push(ctx)
    }
}

impl<'vm, 'value, T> Getable<'vm, 'value> for GluonUser<T>
where
    T: Getable<'vm, 'value>,
{
    impl_getable_simple!();

    fn from_value(vm: &'vm Thread, data: Variants<'value>) -> GluonUser<T> {
        // get the data, it must be a complex type
        let data = match data.as_ref() {
            ValueRef::Data(data) => data,
            _ => panic!("Value is not a complex type"),
        };

        GluonUser {
            inner: User {
                // lookup the fields by name; in case of a tuple variant we
                // would use Data::get_variant instead
                name: String::from_value(vm, data.lookup_field(vm, "name").unwrap()),
                age: u32::from_value(vm, data.lookup_field(vm, "age").unwrap()),
                data: T::from_value(vm, data.lookup_field(vm, "data").unwrap()),
            },
        }
    }
}

fn marshal_wrapper() -> Result<()> {
    let vm = new_vm();
    let mut compiler = Compiler::new();

    let src = r#"
        type User a = { name: String, age: Int, data: a }
        { User }
    "#;

    fn load_mod(vm: &Thread) -> vm::Result<ExternModule> {
        let module = record! {
            roundtrip => primitive!(1, |user: GluonUser<OpaqueValue<RootedThread, A>>| {
                println!("name: {}, age: {}", user.inner.name, user.inner.age);
                user
            }),
        };

        ExternModule::new(vm, module)
    }

    compiler.load_script(&vm, "examples.wrapper", src).unwrap();
    import::add_extern_module(&vm, "examples.prim", load_mod);

    let script = r#"
        let { User } = import! examples.wrapper
        let { roundtrip } = import! examples.prim
        let { assert } = import! std.test

        let actual = { name = "Bob", age = 11, data = True }
        let expected = roundtrip actual

        assert (actual.name == expected.name)
        assert (actual.age == expected.age)
        assert (actual.data == expected.data)
    "#;

    compiler.run_expr::<()>(&vm, "example", script)?;

    Ok(())
}

#[derive(Userdata, Trace, Debug, Clone, VmType)]
#[gluon(vm_type = "WindowHandle")]
struct WindowHandle {
    id: Arc<u64>,
    metadata: Arc<str>,
}

fn load_mod(vm: &gluon::Thread) -> vm::Result<ExternModule> {
    vm.register_type::<WindowHandle>("WindowHandle", &[])?;

    let module = record! {
        create_hwnd => primitive!(2, create_hwnd),
        id => primitive!(1, id),
        metadata => primitive!(1, metadata),
    };

    ExternModule::new(vm, module)
}

fn create_hwnd(id: u64, metadata: String) -> WindowHandle {
    WindowHandle {
        id: Arc::new(id),
        metadata: Arc::from(metadata),
    }
}

fn id(hwnd: &WindowHandle) -> u64 {
    *hwnd.id
}

fn metadata(hwnd: &WindowHandle) -> String {
    String::from(&*hwnd.metadata)
}

fn marshal_userdata() -> Result<()> {
    let vm = new_vm();
    let mut compiler = gluon::Compiler::new();

    gluon::import::add_extern_module(&vm, "hwnd", load_mod);

    // Load the extern module so that the next run_expr call can access the registered type
    compiler.run_expr::<OpaqueValue<&Thread, Hole>>(&vm, "", "import! hwnd")?;

    let script = r#"
        let { assert } = import! std.test
        let { create_hwnd, id, metadata } = import! hwnd
        let hwnd = create_hwnd 0 "Window1"
        assert (id hwnd == 0)
        assert (metadata hwnd == "Window1")
        hwnd
    "#;

    // `UserdataValue` lets us extract a `Clone` of its inner userdata value
    let (UserdataValue(handle), _) =
        compiler.run_expr::<UserdataValue<WindowHandle>>(&vm, "test", script)?;
    assert_eq!(*handle.id, 0);
    assert_eq!(&*handle.metadata, "Window1");

    // If cloning would be expansive we can instate use `OpaqueValue` to get a smart pointer to the
    // userdata which implements `Deref` for easy access
    let (handle, _) =
        compiler.run_expr::<OpaqueValue<&Thread, WindowHandle>>(&vm, "test", script)?;
    assert_eq!(*handle.id, 0);
    assert_eq!(&*handle.metadata, "Window1");
    Ok(())
}

fn main() {
    env_logger::init();

    if let Err(err) = marshal_enum() {
        eprintln!("{}", err)
    }

    let mut map = HashMap::new();
    map.insert("key".to_string(), "value".to_string());
    map.insert("key2".to_string(), "value2".to_string());

    if let Err(err) = marshal_map(map) {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_generic() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_wrapper() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_userdata() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }
}
<commit_msg>doc: Show how to marshal a recursive gluon value<commit_after>#[macro_use]
extern crate gluon_codegen;
extern crate gluon;
#[macro_use]
extern crate gluon_vm;

extern crate env_logger;
#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;
use std::sync::Arc;

use gluon::base::types::ArcType;

use gluon::base::types::{AppVec, Type};
use gluon::vm::api::generic::{A, L, R};
use gluon::vm::api::{
    self, ActiveThread, FunctionRef, Getable, Hole, OpaqueRef, OpaqueValue, Pushable,
    UserdataValue, ValueRef, VmType, IO,
};
use gluon::vm::{self, ExternModule, Variants};
use gluon::{import, new_vm, Compiler, Result, RootedThread, Thread};

#[derive(Debug, Deserialize, Serialize)]
enum Enum {
    A,
    B(i32),
    C(String, String),
}

impl api::VmType for Enum {
    type Type = Self;
    fn make_type(thread: &Thread) -> ArcType {
        thread
            .find_type_info("examples.enum.Enum")
            .unwrap()
            .clone()
            .into_type()
    }
}

impl<'vm, 'value> api::Pushable<'vm> for Enum {
    fn push(self, context: &mut ActiveThread<'vm>) -> vm::Result<()> {
        api::ser::Ser(self).push(context)
    }
}

impl<'vm, 'value> api::Getable<'vm, 'value> for Enum {
    impl_getable_simple!();
    fn from_value(thread: &'vm Thread, value: vm::Variants<'value>) -> Self {
        api::de::De::from_value(thread, value).0
    }
}

field_decl! { unwrap_b, value, key }

// we define Either with type parameters, just like in Gluon
#[derive(Getable, Pushable, VmType)]
#[gluon(vm_type = "examples.either.Either")]
enum Either<L, R> {
    Left(L),
    Right(R),
}

fn marshal_enum() -> Result<()> {
    let thread = new_vm();

    let enum_source = api::typ::make_source::<Enum>(&thread)?;
    Compiler::new().load_script(&thread, "examples.enum", &enum_source)?;

    let source = r#"
        let { Enum } = import! "examples/enum.glu"

        let unwrap_b x =
            match x with
            | B y -> y
            | _ -> error "Expected B"

        {
            unwrap_b,
            value = C "hello" "world"
        }
    "#;
    type SourceType<'thread> = record_type! {
        unwrap_b => api::FunctionRef<'thread, fn (Enum) -> i32>,
        value => Enum
    };
    let (record_p! { mut unwrap_b, value }, _) =
        Compiler::new().run_expr::<SourceType>(&thread, "example", source)?;
    match value {
        Enum::C(ref a, ref b) => {
            assert_eq!(a, "hello");
            assert_eq!(b, "world");
            println!("`value` evaluated to: {:?}", value)
        }
        _ => panic!("Unexpected result returned"),
    }

    let x = unwrap_b.call(Enum::B(123))?;
    assert_eq!(x, 123);
    println!("`unwrap_b` returned: {}", x);

    Ok(())
}

fn marshal_map<I>(iterable: I) -> Result<()>
where
    I: IntoIterator<Item = (String, String)>,
{
    let thread = new_vm();

    // Load std.map so that we can retrieve the `Map` type through the `VmType` trait
    Compiler::new().run_expr::<()>(&thread, "example", "let _ = import! std.map in ()")?;

    let config_example = r#"
        let array = import! std.array
        let map @ { Map } = import! std.map

        let run config_array =
            let f m entry : Map String String -> (String, String) -> _ =
                map.insert entry._0 entry._1 m
            array.foldable.foldl f map.empty config_array
        run
        "#;
    let mut make_map: FunctionRef<
        fn(Vec<(String, String)>) -> OpaqueValue<RootedThread, api::Map<String, String>>,
    > = Compiler::new()
        .run_expr(&thread, "example", config_example)?
        .0;

    let entries: Vec<_> = iterable.into_iter().collect();
    let map = make_map.call(entries)?;

    let config_query_example = r#"
        let map = import! std.map

        let run config_map =
            (map.find "key" config_map, map.find "undefined" config_map)
        run
        "#;
    let mut query_map: FunctionRef<
        fn(OpaqueValue<RootedThread, api::Map<String, String>>) -> (Option<String>, Option<String>),
    > = Compiler::new()
        .run_expr(&thread, "example", config_query_example)?
        .0;

    let tuple = query_map.call(map)?;
    assert_eq!(tuple, (Some("value".to_string()), None));
    println!("Querying the map returned: {:?}", tuple);

    Ok(())
}

// the function takes an Either instantiated with the `Opaque*` struct,
// which will handle the generic Gluon values for us.
//
// We use the `OpaqueRef` alias here since we only need temporary (borrowed) access and it avoids
// the cost of reference counted rooting.
fn flip<'a>(
    either: Either<OpaqueRef<'a, L>, OpaqueRef<'a, R>>,
) -> Either<OpaqueRef<'a, R>, OpaqueRef<'a, L>> {
    match either {
        Either::Left(val) => Either::Right(val),
        Either::Right(val) => Either::Left(val),
    }
}

fn marshal_generic() -> Result<()> {
    let vm = new_vm();
    let mut compiler = Compiler::new();

    // define the gluon type that maps to the rust Either
    let src = r#"
        type Either l r = | Left l | Right r
        { Either }
    "#;

    // load the type and then the module containing the rust function
    fn load_mod(vm: &Thread) -> vm::Result<ExternModule> {
        let module = record! {
            flip => primitive!(1, flip),
        };

        ExternModule::new(vm, module)
    }

    compiler.load_script(&vm, "examples.either", src).unwrap();
    import::add_extern_module(&vm, "examples.prim", load_mod);

    let script = r#"
        let { Either } = import! examples.either
        let { flip } = import! examples.prim
        let { (<>) } = import! std.semigroup
        let io @ { flat_map } = import! std.io

        // Either is defined as:
        // type Either l r = | Left l | Right r
        let either: forall r . Either String r = Left "hello rust!"

        // we can pass the generic Either to the Rust function without an issue
        seq
            match flip either with
            | Left _ -> error "unreachable!"
            | Right val -> io.println ("Right is: " <> val)

        // using an Int instead also works
        let either: forall r . Either Int r = Left 42

        match flip either with
        | Left _ -> error "also unreachable!"
        | Right 42 -> io.println "this is the right answer"
        | Right _ -> error "wrong answer!"
    "#;

    compiler
        .run_io(true)
        .run_expr::<IO<()>>(&vm, "example", script)?;

    Ok(())
}

// if this were a foreign type, we would not be able to
// directly implement traits for it
#[derive(Debug)]
struct User<T> {
    name: String,
    age: u32,
    data: T,
}

// but it's possible to wrap it and manually implement traits for it
struct GluonUser<T> {
    inner: User<T>,
}

impl<T> VmType for GluonUser<T>
where
    T: 'static + VmType,
{
    type Type = Self;

    fn make_type(vm: &Thread) -> ArcType {
        // get the type defined in Gluon
        let ty = vm
            .find_type_info("examples.wrapper.User")
            .expect("Could not find type")
            .into_type();

        // apply all generic parameters to the type
        let mut vec = AppVec::new();
        vec.push(T::make_type(vm));
        Type::app(ty, vec)
    }
}

impl<'vm, T> Pushable<'vm> for GluonUser<T>
where
    T: Pushable<'vm>,
{
    fn push(self, ctx: &mut ActiveThread<'vm>) -> vm::Result<()> {
        (record! {
            name => self.inner.name,
            age => self.inner.age,
            data => self.inner.data,
        })
        .push(ctx)
    }
}

impl<'vm, 'value, T> Getable<'vm, 'value> for GluonUser<T>
where
    T: Getable<'vm, 'value>,
{
    impl_getable_simple!();

    fn from_value(vm: &'vm Thread, data: Variants<'value>) -> GluonUser<T> {
        // get the data, it must be a complex type
        let data = match data.as_ref() {
            ValueRef::Data(data) => data,
            _ => panic!("Value is not a complex type"),
        };

        GluonUser {
            inner: User {
                // lookup the fields by name; in case of a tuple variant we
                // would use Data::get_variant instead
                name: String::from_value(vm, data.lookup_field(vm, "name").unwrap()),
                age: u32::from_value(vm, data.lookup_field(vm, "age").unwrap()),
                data: T::from_value(vm, data.lookup_field(vm, "data").unwrap()),
            },
        }
    }
}

fn marshal_wrapper() -> Result<()> {
    let vm = new_vm();
    let mut compiler = Compiler::new();

    let src = r#"
        type User a = { name: String, age: Int, data: a }
        { User }
    "#;

    fn load_mod(vm: &Thread) -> vm::Result<ExternModule> {
        let module = record! {
            roundtrip => primitive!(1, |user: GluonUser<OpaqueValue<RootedThread, A>>| {
                println!("name: {}, age: {}", user.inner.name, user.inner.age);
                user
            }),
        };

        ExternModule::new(vm, module)
    }

    compiler.load_script(&vm, "examples.wrapper", src).unwrap();
    import::add_extern_module(&vm, "examples.prim", load_mod);

    let script = r#"
        let { User } = import! examples.wrapper
        let { roundtrip } = import! examples.prim
        let { assert } = import! std.test

        let actual = { name = "Bob", age = 11, data = True }
        let expected = roundtrip actual

        assert (actual.name == expected.name)
        assert (actual.age == expected.age)
        assert (actual.data == expected.data)
    "#;

    compiler.run_expr::<()>(&vm, "example", script)?;

    Ok(())
}

#[derive(Userdata, Trace, Debug, Clone, VmType)]
#[gluon(vm_type = "WindowHandle")]
struct WindowHandle {
    id: Arc<u64>,
    metadata: Arc<str>,
}

fn load_mod(vm: &gluon::Thread) -> vm::Result<ExternModule> {
    vm.register_type::<WindowHandle>("WindowHandle", &[])?;

    let module = record! {
        create_hwnd => primitive!(2, create_hwnd),
        id => primitive!(1, id),
        metadata => primitive!(1, metadata),
    };

    ExternModule::new(vm, module)
}

fn create_hwnd(id: u64, metadata: String) -> WindowHandle {
    WindowHandle {
        id: Arc::new(id),
        metadata: Arc::from(metadata),
    }
}

fn id(hwnd: &WindowHandle) -> u64 {
    *hwnd.id
}

fn metadata(hwnd: &WindowHandle) -> String {
    String::from(&*hwnd.metadata)
}

fn marshal_userdata() -> Result<()> {
    let vm = new_vm();
    let mut compiler = gluon::Compiler::new();

    gluon::import::add_extern_module(&vm, "hwnd", load_mod);

    // Load the extern module so that the next run_expr call can access the registered type
    compiler.run_expr::<OpaqueValue<&Thread, Hole>>(&vm, "", "import! hwnd")?;

    let script = r#"
        let { assert } = import! std.test
        let { create_hwnd, id, metadata } = import! hwnd
        let hwnd = create_hwnd 0 "Window1"
        assert (id hwnd == 0)
        assert (metadata hwnd == "Window1")
        hwnd
    "#;

    // `UserdataValue` lets us extract a `Clone` of its inner userdata value
    let (UserdataValue(handle), _) =
        compiler.run_expr::<UserdataValue<WindowHandle>>(&vm, "test", script)?;
    assert_eq!(*handle.id, 0);
    assert_eq!(&*handle.metadata, "Window1");

    // If cloning would be expansive we can instate use `OpaqueValue` to get a smart pointer to the
    // userdata which implements `Deref` for easy access
    let (handle, _) =
        compiler.run_expr::<OpaqueValue<&Thread, WindowHandle>>(&vm, "test", script)?;
    assert_eq!(*handle.id, 0);
    assert_eq!(&*handle.metadata, "Window1");
    Ok(())
}

#[derive(Debug, PartialEq, VmType, Getable)]
// Deriving `VmType` won't work for a recursive type at the moment so we need to point to a type
// specified in gluon itself
#[gluon(vm_type = "std.list.List")]
enum List<T> {
    Nil,
    Cons(T, Box<List<T>>),
}

fn marshal_recursive() -> Result<()> {
    let vm = new_vm();
    let mut compiler = gluon::Compiler::new();

    // Load std.list before we try to use it in `VmType for List`
    compiler.run_expr::<OpaqueValue<RootedThread, Hole>>(&vm, "example", "import! std.list")?;

    let source = r#"
        let list = import! std.list
        list.of [1, 2]
        "#;
    let (list, _) = compiler.run_expr::<List<i32>>(&vm, "example", source)?;

    assert_eq!(list, List::Cons(1, List::Cons(2, List::Nil.into()).into()));
    println!("The list {:?}", list);
    Ok(())
}

fn main() {
    env_logger::init();

    if let Err(err) = marshal_enum() {
        eprintln!("{}", err)
    }

    let mut map = HashMap::new();
    map.insert("key".to_string(), "value".to_string());
    map.insert("key2".to_string(), "value2".to_string());

    if let Err(err) = marshal_map(map) {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_generic() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_wrapper() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_userdata() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }

    if let Err(err) = marshal_recursive() {
        eprintln!("{}", err);
        ::std::process::exit(1);
    }
}
<|endoftext|>


<commit_before>use super::*;

#[cfg(unix)]
use std::os::unix::fs::FileExt;

/// Multithreaded IO support for Files
pub(crate) trait Pio {
    /// Read from a specific offset without changing
    /// the underlying file offset.
    fn pread_exact(&self, to_buf: &mut [u8], offset: LogId) -> io::Result<()>;

    /// Write to a specific offset without changing
    /// the underlying file offset.
    fn pwrite_all(&self, from_buf: &[u8], offset: LogId) -> io::Result<()>;
}

// On systems that support pread/pwrite, use them underneath.
#[cfg(unix)]
impl Pio for std::fs::File {
    fn pread_exact(
        &self,
        mut buf: &mut [u8],
        mut offset: LogId,
    ) -> io::Result<()> {
        while !buf.is_empty() {
            match self.read_at(buf, offset) {
                Ok(0) => break,
                Ok(n) => {
                    offset += n as LogId;
                    let tmp = buf;
                    buf = &mut tmp[n..];
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        if !buf.is_empty() {
            Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                "failed to fill whole buffer",
            ))
        } else {
            Ok(())
        }
    }

    fn pwrite_all(&self, mut buf: &[u8], mut offset: LogId) -> io::Result<()> {
        while !buf.is_empty() {
            match self.write_at(buf, offset) {
                Ok(0) => {
                    return Err(io::Error::new(
                        io::ErrorKind::WriteZero,
                        "failed to write whole buffer",
                    ));
                }
                Ok(n) => {
                    offset += n as LogId;
                    buf = &buf[n..]
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        Ok(())
    }
}

#[cfg(not(unix))]
use std::{
    io::{Read, Seek, Write},
    sync::Mutex,
};

#[cfg(not(unix))]
lazy_static! {
    pub(crate) static ref GLOBAL_FILE_LOCK: Mutex<()> = Mutex::new(());
}

#[cfg(not(unix))]
impl Pio for std::fs::File {
    fn pread_exact(&self, mut buf: &mut [u8], offset: LogId) -> io::Result<()> {
        let _lock = GLOBAL_FILE_LOCK.lock().unwrap();

        let mut f = self.try_clone()?;

        f.seek(std::io::SeekFrom::Start(offset))?;

        while !buf.is_empty() {
            match f.read(buf) {
                Ok(0) => break,
                Ok(n) => {
                    let tmp = buf;
                    buf = &mut tmp[n..];
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        if !buf.is_empty() {
            Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                "failed to fill whole buffer",
            ))
        } else {
            Ok(())
        }
    }

    fn pwrite_all(&self, mut buf: &[u8], offset: LogId) -> io::Result<()> {
        let _lock = GLOBAL_FILE_LOCK.lock().unwrap();

        let mut f = self.try_clone()?;

        f.seek(std::io::SeekFrom::Start(offset))?;

        while !buf.is_empty() {
            match f.write(buf) {
                Ok(0) => {
                    return Err(io::Error::new(
                        io::ErrorKind::WriteZero,
                        "failed to write whole buffer",
                    ));
                }
                Ok(n) => buf = &buf[n..],
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        Ok(())
    }
}
<commit_msg>Remove logic for handling pread_exact/pwrite_all that has been added to std<commit_after>use super::*;

#[cfg(unix)]
use std::os::unix::fs::FileExt;

/// Multithreaded IO support for Files
pub(crate) trait Pio {
    /// Read from a specific offset without changing
    /// the underlying file offset.
    fn pread_exact(&self, to_buf: &mut [u8], offset: LogId) -> io::Result<()>;

    /// Write to a specific offset without changing
    /// the underlying file offset.
    fn pwrite_all(&self, from_buf: &[u8], offset: LogId) -> io::Result<()>;
}

// On systems that support pread/pwrite, use them underneath.
#[cfg(unix)]
impl Pio for std::fs::File {
    fn pread_exact(&self, buf: &mut [u8], offset: LogId) -> io::Result<()> {
        self.read_exact_at(buf, offset)
    }

    fn pwrite_all(&self, buf: &[u8], offset: LogId) -> io::Result<()> {
        self.write_all_at(buf, offset)
    }
}

#[cfg(not(unix))]
use std::{
    io::{Read, Seek, Write},
    sync::Mutex,
};

#[cfg(not(unix))]
lazy_static! {
    pub(crate) static ref GLOBAL_FILE_LOCK: Mutex<()> = Mutex::new(());
}

#[cfg(not(unix))]
impl Pio for std::fs::File {
    fn pread_exact(&self, mut buf: &mut [u8], offset: LogId) -> io::Result<()> {
        let _lock = GLOBAL_FILE_LOCK.lock().unwrap();

        let mut f = self.try_clone()?;

        f.seek(std::io::SeekFrom::Start(offset))?;

        while !buf.is_empty() {
            match f.read(buf) {
                Ok(0) => break,
                Ok(n) => {
                    let tmp = buf;
                    buf = &mut tmp[n..];
                }
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        if !buf.is_empty() {
            Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                "failed to fill whole buffer",
            ))
        } else {
            Ok(())
        }
    }

    fn pwrite_all(&self, mut buf: &[u8], offset: LogId) -> io::Result<()> {
        let _lock = GLOBAL_FILE_LOCK.lock().unwrap();

        let mut f = self.try_clone()?;

        f.seek(std::io::SeekFrom::Start(offset))?;

        while !buf.is_empty() {
            match f.write(buf) {
                Ok(0) => {
                    return Err(io::Error::new(
                        io::ErrorKind::WriteZero,
                        "failed to write whole buffer",
                    ));
                }
                Ok(n) => buf = &buf[n..],
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
                Err(e) => return Err(e),
            }
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};

use itertools::Itertools;
use rand::{seq::SliceRandom, thread_rng};

use hir::{
    db::{DefDatabase, HirDatabase},
    AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
};
use hir_def::FunctionId;
use hir_ty::{Ty, TypeWalk};
use ra_db::SourceDatabaseExt;
use ra_syntax::AstNode;

use crate::{progress_report::ProgressReport, Result, Verbosity};

pub fn run(
    verbosity: Verbosity,
    memory_usage: bool,
    path: &Path,
    only: Option<&str>,
    with_deps: bool,
    randomize: bool,
) -> Result<()> {
    let db_load_time = Instant::now();
    let (mut host, roots) = ra_batch::load_cargo(path)?;
    let db = host.raw_database();
    println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
    let analysis_time = Instant::now();
    let mut num_crates = 0;
    let mut visited_modules = HashSet::new();
    let mut visit_queue = Vec::new();

    let members =
        roots
            .into_iter()
            .filter_map(|(source_root_id, project_root)| {
                if with_deps || project_root.is_member() {
                    Some(source_root_id)
                } else {
                    None
                }
            })
            .collect::<HashSet<_>>();

    let mut krates = Crate::all(db);
    if randomize {
        krates.shuffle(&mut thread_rng());
    }
    for krate in krates {
        let module = krate.root_module(db).expect("crate without root module");
        let file_id = module.definition_source(db).file_id;
        if members.contains(&db.file_source_root(file_id.original_file(db))) {
            num_crates += 1;
            visit_queue.push(module);
        }
    }

    if randomize {
        visit_queue.shuffle(&mut thread_rng());
    }

    println!("Crates in this dir: {}", num_crates);
    let mut num_decls = 0;
    let mut funcs = Vec::new();
    while let Some(module) = visit_queue.pop() {
        if visited_modules.insert(module) {
            visit_queue.extend(module.children(db));

            for decl in module.declarations(db) {
                num_decls += 1;
                if let ModuleDef::Function(f) = decl {
                    funcs.push(f);
                }
            }

            for impl_block in module.impl_blocks(db) {
                for item in impl_block.items(db) {
                    num_decls += 1;
                    if let AssocItem::Function(f) = item {
                        funcs.push(f);
                    }
                }
            }
        }
    }
    println!("Total modules found: {}", visited_modules.len());
    println!("Total declarations: {}", num_decls);
    println!("Total functions: {}", funcs.len());
    println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());

    if randomize {
        funcs.shuffle(&mut thread_rng());
    }

    let inference_time = Instant::now();
    let mut bar = match verbosity {
        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
        _ => ProgressReport::new(funcs.len() as u64),
    };

    bar.tick();
    let mut num_exprs = 0;
    let mut num_exprs_unknown = 0;
    let mut num_exprs_partially_unknown = 0;
    let mut num_type_mismatches = 0;
    for f in funcs {
        let name = f.name(db);
        let full_name = f
            .module(db)
            .path_to_root(db)
            .into_iter()
            .rev()
            .filter_map(|it| it.name(db))
            .chain(Some(f.name(db)))
            .join("::");
        if let Some(only_name) = only {
            if name.to_string() != only_name && full_name != only_name {
                continue;
            }
        }
        let mut msg = format!("processing: {}", full_name);
        if verbosity.is_verbose() {
            let src = f.source(db);
            let original_file = src.file_id.original_file(db);
            let path = db.file_relative_path(original_file);
            let syntax_range = src.value.syntax().text_range();
            write!(msg, " ({:?} {})", path, syntax_range).unwrap();
        }
        if verbosity.is_spammy() {
            bar.println(format!("{}", msg));
        }
        bar.set_message(&msg);
        let f_id = FunctionId::from(f);
        let body = db.body(f_id.into());
        let inference_result = db.infer(f_id.into());
        let (previous_exprs, previous_unknown, previous_partially_unknown) =
            (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
        for (expr_id, _) in body.exprs.iter() {
            let ty = &inference_result[expr_id];
            num_exprs += 1;
            if let Ty::Unknown = ty {
                num_exprs_unknown += 1;
            } else {
                let mut is_partially_unknown = false;
                ty.walk(&mut |ty| {
                    if let Ty::Unknown = ty {
                        is_partially_unknown = true;
                    }
                });
                if is_partially_unknown {
                    num_exprs_partially_unknown += 1;
                }
            }
            if only.is_some() && verbosity.is_spammy() {
                // in super-verbose mode for just one function, we print every single expression
                let (_, sm) = db.body_with_source_map(f_id.into());
                let src = sm.expr_syntax(expr_id);
                if let Some(src) = src {
                    let original_file = src.file_id.original_file(db);
                    let line_index = host.analysis().file_line_index(original_file).unwrap();
                    let text_range = src.value.either(
                        |it| it.syntax_node_ptr().range(),
                        |it| it.syntax_node_ptr().range(),
                    );
                    let (start, end) = (
                        line_index.line_col(text_range.start()),
                        line_index.line_col(text_range.end()),
                    );
                    bar.println(format!(
                        "{}:{}-{}:{}: {}",
                        start.line + 1,
                        start.col_utf16,
                        end.line + 1,
                        end.col_utf16,
                        ty.display(db)
                    ));
                } else {
                    bar.println(format!("unknown location: {}", ty.display(db)));
                }
            }
            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
                num_type_mismatches += 1;
                if verbosity.is_verbose() {
                    let (_, sm) = db.body_with_source_map(f_id.into());
                    let src = sm.expr_syntax(expr_id);
                    if let Some(src) = src {
                        // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
                        let original_file = src.file_id.original_file(db);
                        let path = db.file_relative_path(original_file);
                        let line_index = host.analysis().file_line_index(original_file).unwrap();
                        let text_range = src.value.either(
                            |it| it.syntax_node_ptr().range(),
                            |it| it.syntax_node_ptr().range(),
                        );
                        let (start, end) = (
                            line_index.line_col(text_range.start()),
                            line_index.line_col(text_range.end()),
                        );
                        bar.println(format!(
                            "{} {}:{}-{}:{}: Expected {}, got {}",
                            path,
                            start.line + 1,
                            start.col_utf16,
                            end.line + 1,
                            end.col_utf16,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    } else {
                        bar.println(format!(
                            "{}: Expected {}, got {}",
                            name,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    }
                }
            }
        }
        if verbosity.is_spammy() {
            bar.println(format!(
                "In {}: {} exprs, {} unknown, {} partial",
                full_name,
                num_exprs - previous_exprs,
                num_exprs_unknown - previous_unknown,
                num_exprs_partially_unknown - previous_partially_unknown
            ));
        }
        bar.inc(1);
    }
    bar.finish_and_clear();
    println!("Total expressions: {}", num_exprs);
    println!(
        "Expressions of unknown type: {} ({}%)",
        num_exprs_unknown,
        if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
    );
    println!(
        "Expressions of partially unknown type: {} ({}%)",
        num_exprs_partially_unknown,
        if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
    );
    println!("Type mismatches: {}", num_type_mismatches);
    println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
    println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());

    if memory_usage {
        for (name, bytes) in host.per_query_memory_usage() {
            println!("{:>8} {}", bytes, name)
        }
        let before = ra_prof::memory_usage();
        drop(host);
        println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated)
    }

    Ok(())
}
<commit_msg>Cleanup imports<commit_after>//! FIXME: write short doc here

use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};

use hir::{
    db::{DefDatabase, HirDatabase},
    AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
};
use hir_def::FunctionId;
use hir_ty::{Ty, TypeWalk};
use itertools::Itertools;
use ra_db::SourceDatabaseExt;
use ra_syntax::AstNode;
use rand::{seq::SliceRandom, thread_rng};

use crate::{progress_report::ProgressReport, Result, Verbosity};

pub fn run(
    verbosity: Verbosity,
    memory_usage: bool,
    path: &Path,
    only: Option<&str>,
    with_deps: bool,
    randomize: bool,
) -> Result<()> {
    let db_load_time = Instant::now();
    let (mut host, roots) = ra_batch::load_cargo(path)?;
    let db = host.raw_database();
    println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
    let analysis_time = Instant::now();
    let mut num_crates = 0;
    let mut visited_modules = HashSet::new();
    let mut visit_queue = Vec::new();

    let members =
        roots
            .into_iter()
            .filter_map(|(source_root_id, project_root)| {
                if with_deps || project_root.is_member() {
                    Some(source_root_id)
                } else {
                    None
                }
            })
            .collect::<HashSet<_>>();

    let mut krates = Crate::all(db);
    if randomize {
        krates.shuffle(&mut thread_rng());
    }
    for krate in krates {
        let module = krate.root_module(db).expect("crate without root module");
        let file_id = module.definition_source(db).file_id;
        if members.contains(&db.file_source_root(file_id.original_file(db))) {
            num_crates += 1;
            visit_queue.push(module);
        }
    }

    if randomize {
        visit_queue.shuffle(&mut thread_rng());
    }

    println!("Crates in this dir: {}", num_crates);
    let mut num_decls = 0;
    let mut funcs = Vec::new();
    while let Some(module) = visit_queue.pop() {
        if visited_modules.insert(module) {
            visit_queue.extend(module.children(db));

            for decl in module.declarations(db) {
                num_decls += 1;
                if let ModuleDef::Function(f) = decl {
                    funcs.push(f);
                }
            }

            for impl_block in module.impl_blocks(db) {
                for item in impl_block.items(db) {
                    num_decls += 1;
                    if let AssocItem::Function(f) = item {
                        funcs.push(f);
                    }
                }
            }
        }
    }
    println!("Total modules found: {}", visited_modules.len());
    println!("Total declarations: {}", num_decls);
    println!("Total functions: {}", funcs.len());
    println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());

    if randomize {
        funcs.shuffle(&mut thread_rng());
    }

    let inference_time = Instant::now();
    let mut bar = match verbosity {
        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
        _ => ProgressReport::new(funcs.len() as u64),
    };

    bar.tick();
    let mut num_exprs = 0;
    let mut num_exprs_unknown = 0;
    let mut num_exprs_partially_unknown = 0;
    let mut num_type_mismatches = 0;
    for f in funcs {
        let name = f.name(db);
        let full_name = f
            .module(db)
            .path_to_root(db)
            .into_iter()
            .rev()
            .filter_map(|it| it.name(db))
            .chain(Some(f.name(db)))
            .join("::");
        if let Some(only_name) = only {
            if name.to_string() != only_name && full_name != only_name {
                continue;
            }
        }
        let mut msg = format!("processing: {}", full_name);
        if verbosity.is_verbose() {
            let src = f.source(db);
            let original_file = src.file_id.original_file(db);
            let path = db.file_relative_path(original_file);
            let syntax_range = src.value.syntax().text_range();
            write!(msg, " ({:?} {})", path, syntax_range).unwrap();
        }
        if verbosity.is_spammy() {
            bar.println(format!("{}", msg));
        }
        bar.set_message(&msg);
        let f_id = FunctionId::from(f);
        let body = db.body(f_id.into());
        let inference_result = db.infer(f_id.into());
        let (previous_exprs, previous_unknown, previous_partially_unknown) =
            (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
        for (expr_id, _) in body.exprs.iter() {
            let ty = &inference_result[expr_id];
            num_exprs += 1;
            if let Ty::Unknown = ty {
                num_exprs_unknown += 1;
            } else {
                let mut is_partially_unknown = false;
                ty.walk(&mut |ty| {
                    if let Ty::Unknown = ty {
                        is_partially_unknown = true;
                    }
                });
                if is_partially_unknown {
                    num_exprs_partially_unknown += 1;
                }
            }
            if only.is_some() && verbosity.is_spammy() {
                // in super-verbose mode for just one function, we print every single expression
                let (_, sm) = db.body_with_source_map(f_id.into());
                let src = sm.expr_syntax(expr_id);
                if let Some(src) = src {
                    let original_file = src.file_id.original_file(db);
                    let line_index = host.analysis().file_line_index(original_file).unwrap();
                    let text_range = src.value.either(
                        |it| it.syntax_node_ptr().range(),
                        |it| it.syntax_node_ptr().range(),
                    );
                    let (start, end) = (
                        line_index.line_col(text_range.start()),
                        line_index.line_col(text_range.end()),
                    );
                    bar.println(format!(
                        "{}:{}-{}:{}: {}",
                        start.line + 1,
                        start.col_utf16,
                        end.line + 1,
                        end.col_utf16,
                        ty.display(db)
                    ));
                } else {
                    bar.println(format!("unknown location: {}", ty.display(db)));
                }
            }
            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
                num_type_mismatches += 1;
                if verbosity.is_verbose() {
                    let (_, sm) = db.body_with_source_map(f_id.into());
                    let src = sm.expr_syntax(expr_id);
                    if let Some(src) = src {
                        // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
                        let original_file = src.file_id.original_file(db);
                        let path = db.file_relative_path(original_file);
                        let line_index = host.analysis().file_line_index(original_file).unwrap();
                        let text_range = src.value.either(
                            |it| it.syntax_node_ptr().range(),
                            |it| it.syntax_node_ptr().range(),
                        );
                        let (start, end) = (
                            line_index.line_col(text_range.start()),
                            line_index.line_col(text_range.end()),
                        );
                        bar.println(format!(
                            "{} {}:{}-{}:{}: Expected {}, got {}",
                            path,
                            start.line + 1,
                            start.col_utf16,
                            end.line + 1,
                            end.col_utf16,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    } else {
                        bar.println(format!(
                            "{}: Expected {}, got {}",
                            name,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    }
                }
            }
        }
        if verbosity.is_spammy() {
            bar.println(format!(
                "In {}: {} exprs, {} unknown, {} partial",
                full_name,
                num_exprs - previous_exprs,
                num_exprs_unknown - previous_unknown,
                num_exprs_partially_unknown - previous_partially_unknown
            ));
        }
        bar.inc(1);
    }
    bar.finish_and_clear();
    println!("Total expressions: {}", num_exprs);
    println!(
        "Expressions of unknown type: {} ({}%)",
        num_exprs_unknown,
        if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
    );
    println!(
        "Expressions of partially unknown type: {} ({}%)",
        num_exprs_partially_unknown,
        if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
    );
    println!("Type mismatches: {}", num_type_mismatches);
    println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
    println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());

    if memory_usage {
        for (name, bytes) in host.per_query_memory_usage() {
            println!("{:>8} {}", bytes, name)
        }
        let before = ra_prof::memory_usage();
        drop(host);
        println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated)
    }

    Ok(())
}
<|endoftext|>


<commit_before>//! A map of all publicly exported items in a crate.

use std::{collections::hash_map::Entry, fmt, sync::Arc};

use ra_db::CrateId;
use rustc_hash::FxHashMap;

use crate::{
    db::DefDatabase,
    item_scope::ItemInNs,
    path::{ModPath, PathKind},
    visibility::Visibility,
    ModuleDefId, ModuleId,
};

/// A map from publicly exported items to the path needed to import/name them from a downstream
/// crate.
///
/// Reexports of items are taken into account, ie. if something is exported under multiple
/// names, the one with the shortest import path will be used.
///
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
/// to be prepended to the `ModPath` before the path is valid.
#[derive(Eq, PartialEq)]
pub struct ImportMap {
    map: FxHashMap<ItemInNs, ModPath>,
}

impl ImportMap {
    pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
        let _p = ra_prof::profile("import_map_query");
        let def_map = db.crate_def_map(krate);
        let mut import_map = FxHashMap::with_capacity_and_hasher(64, Default::default());

        // We look only into modules that are public(ly reexported), starting with the crate root.
        let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
        let root = ModuleId { krate, local_id: def_map.root };
        let mut worklist = vec![(root, empty)];
        while let Some((module, mod_path)) = worklist.pop() {
            let ext_def_map;
            let mod_data = if module.krate == krate {
                &def_map[module.local_id]
            } else {
                // The crate might reexport a module defined in another crate.
                ext_def_map = db.crate_def_map(module.krate);
                &ext_def_map[module.local_id]
            };

            let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
                let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
                if per_ns.is_none() {
                    None
                } else {
                    Some((name, per_ns))
                }
            });

            for (name, per_ns) in visible_items {
                let mk_path = || {
                    let mut path = mod_path.clone();
                    path.segments.push(name.clone());
                    path
                };

                for item in per_ns.iter_items() {
                    let path = mk_path();
                    match import_map.entry(item) {
                        Entry::Vacant(entry) => {
                            entry.insert(path);
                        }
                        Entry::Occupied(mut entry) => {
                            // If the new path is shorter, prefer that one.
                            if path.len() < entry.get().len() {
                                *entry.get_mut() = path;
                            } else {
                                continue;
                            }
                        }
                    }

                    // If we've just added a path to a module, descend into it.
                    if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
                        worklist.push((mod_id, mk_path()));
                    }
                }
            }
        }

        Arc::new(Self { map: import_map })
    }

    /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
    pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
        self.map.get(&item)
    }
}

impl fmt::Debug for ImportMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut importable_paths: Vec<_> = self
            .map
            .iter()
            .map(|(item, modpath)| {
                let ns = match item {
                    ItemInNs::Types(_) => "t",
                    ItemInNs::Values(_) => "v",
                    ItemInNs::Macros(_) => "m",
                };
                format!("- {} ({})", modpath, ns)
            })
            .collect();

        importable_paths.sort();
        f.write_str(&importable_paths.join("\n"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_db::TestDB;
    use insta::assert_snapshot;
    use ra_db::fixture::WithFixture;
    use ra_db::SourceDatabase;

    fn import_map(ra_fixture: &str) -> String {
        let db = TestDB::with_files(ra_fixture);
        let crate_graph = db.crate_graph();

        let import_maps: Vec<_> = crate_graph
            .iter()
            .filter_map(|krate| {
                let cdata = &crate_graph[krate];
                let name = cdata.display_name.as_ref()?;

                let map = db.import_map(krate);

                Some(format!("{}:\n{:?}", name, map))
            })
            .collect();

        import_maps.join("\n")
    }

    #[test]
    fn smoke() {
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib

            mod private {
                pub use lib::Pub;
                pub struct InPrivateModule;
            }

            pub mod publ1 {
                use lib::Pub;
            }

            pub mod real_pub {
                pub use lib::Pub;
            }
            pub mod real_pu2 { // same path length as above
                pub use lib::Pub;
            }

            //- /lib.rs crate:lib
            pub struct Pub {}
            pub struct Pub2; // t + v
            struct Priv;
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - publ1 (t)
        - real_pu2 (t)
        - real_pub (t)
        - real_pub::Pub (t)
        lib:
        - Pub (t)
        - Pub2 (t)
        - Pub2 (v)
        "###);
    }

    #[test]
    fn prefers_shortest_path() {
        let map = import_map(
            r"
            //- /main.rs crate:main

            pub mod sub {
                pub mod subsub {
                    pub struct Def {}
                }

                pub use super::sub::subsub::Def;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - sub (t)
        - sub::Def (t)
        - sub::subsub (t)
        "###);
    }

    #[test]
    fn type_reexport_cross_crate() {
        // Reexports need to be visible from a crate, even if the original crate exports the item
        // at a shorter path.
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::S;
            }
            //- /lib.rs crate:lib
            pub struct S;
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - m (t)
        - m::S (t)
        - m::S (v)
        lib:
        - S (t)
        - S (v)
        "###);
    }

    #[test]
    fn macro_reexport() {
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::pub_macro;
            }
            //- /lib.rs crate:lib
            #[macro_export]
            macro_rules! pub_macro {
                () => {};
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - m (t)
        - m::pub_macro (m)
        lib:
        - pub_macro (m)
        "###);
    }

    #[test]
    fn module_reexport() {
        // Reexporting modules from a dependency adds all contents to the import map.
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub use lib::module as reexported_module;
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - reexported_module (t)
        - reexported_module::S (t)
        - reexported_module::S (v)
        lib:
        - module (t)
        - module::S (t)
        - module::S (v)
        "###);
    }

    #[test]
    fn cyclic_module_reexport() {
        // A cyclic reexport does not hang.
        let map = import_map(
            r"
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
                pub use super::sub::*;
            }

            pub mod sub {
                pub use super::module;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        lib:
        - module (t)
        - module::S (t)
        - module::S (v)
        - sub (t)
        "###);
    }

    #[test]
    fn private_macro() {
        let map = import_map(
            r"
            //- /lib.rs crate:lib
            macro_rules! private_macro {
                () => {};
            }
        ",
        );

        assert_snapshot!(map, @r###"
        lib:
        "###);
    }
}
<commit_msg>Clarify when we visit modules multiple times<commit_after>//! A map of all publicly exported items in a crate.

use std::{collections::hash_map::Entry, fmt, sync::Arc};

use ra_db::CrateId;
use rustc_hash::FxHashMap;

use crate::{
    db::DefDatabase,
    item_scope::ItemInNs,
    path::{ModPath, PathKind},
    visibility::Visibility,
    ModuleDefId, ModuleId,
};

/// A map from publicly exported items to the path needed to import/name them from a downstream
/// crate.
///
/// Reexports of items are taken into account, ie. if something is exported under multiple
/// names, the one with the shortest import path will be used.
///
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
/// to be prepended to the `ModPath` before the path is valid.
#[derive(Eq, PartialEq)]
pub struct ImportMap {
    map: FxHashMap<ItemInNs, ModPath>,
}

impl ImportMap {
    pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
        let _p = ra_prof::profile("import_map_query");
        let def_map = db.crate_def_map(krate);
        let mut import_map = FxHashMap::with_capacity_and_hasher(64, Default::default());

        // We look only into modules that are public(ly reexported), starting with the crate root.
        let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
        let root = ModuleId { krate, local_id: def_map.root };
        let mut worklist = vec![(root, empty)];
        while let Some((module, mod_path)) = worklist.pop() {
            let ext_def_map;
            let mod_data = if module.krate == krate {
                &def_map[module.local_id]
            } else {
                // The crate might reexport a module defined in another crate.
                ext_def_map = db.crate_def_map(module.krate);
                &ext_def_map[module.local_id]
            };

            let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
                let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
                if per_ns.is_none() {
                    None
                } else {
                    Some((name, per_ns))
                }
            });

            for (name, per_ns) in visible_items {
                let mk_path = || {
                    let mut path = mod_path.clone();
                    path.segments.push(name.clone());
                    path
                };

                for item in per_ns.iter_items() {
                    let path = mk_path();
                    match import_map.entry(item) {
                        Entry::Vacant(entry) => {
                            entry.insert(path);
                        }
                        Entry::Occupied(mut entry) => {
                            // If the new path is shorter, prefer that one.
                            if path.len() < entry.get().len() {
                                *entry.get_mut() = path;
                            } else {
                                continue;
                            }
                        }
                    }

                    // If we've just added a path to a module, descend into it. We might traverse
                    // modules multiple times, but only if the new path to it is shorter than the
                    // first (else we `continue` above).
                    if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
                        worklist.push((mod_id, mk_path()));
                    }
                }
            }
        }

        Arc::new(Self { map: import_map })
    }

    /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
    pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> {
        self.map.get(&item)
    }
}

impl fmt::Debug for ImportMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut importable_paths: Vec<_> = self
            .map
            .iter()
            .map(|(item, modpath)| {
                let ns = match item {
                    ItemInNs::Types(_) => "t",
                    ItemInNs::Values(_) => "v",
                    ItemInNs::Macros(_) => "m",
                };
                format!("- {} ({})", modpath, ns)
            })
            .collect();

        importable_paths.sort();
        f.write_str(&importable_paths.join("\n"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_db::TestDB;
    use insta::assert_snapshot;
    use ra_db::fixture::WithFixture;
    use ra_db::SourceDatabase;

    fn import_map(ra_fixture: &str) -> String {
        let db = TestDB::with_files(ra_fixture);
        let crate_graph = db.crate_graph();

        let import_maps: Vec<_> = crate_graph
            .iter()
            .filter_map(|krate| {
                let cdata = &crate_graph[krate];
                let name = cdata.display_name.as_ref()?;

                let map = db.import_map(krate);

                Some(format!("{}:\n{:?}", name, map))
            })
            .collect();

        import_maps.join("\n")
    }

    #[test]
    fn smoke() {
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib

            mod private {
                pub use lib::Pub;
                pub struct InPrivateModule;
            }

            pub mod publ1 {
                use lib::Pub;
            }

            pub mod real_pub {
                pub use lib::Pub;
            }
            pub mod real_pu2 { // same path length as above
                pub use lib::Pub;
            }

            //- /lib.rs crate:lib
            pub struct Pub {}
            pub struct Pub2; // t + v
            struct Priv;
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - publ1 (t)
        - real_pu2 (t)
        - real_pub (t)
        - real_pub::Pub (t)
        lib:
        - Pub (t)
        - Pub2 (t)
        - Pub2 (v)
        "###);
    }

    #[test]
    fn prefers_shortest_path() {
        let map = import_map(
            r"
            //- /main.rs crate:main

            pub mod sub {
                pub mod subsub {
                    pub struct Def {}
                }

                pub use super::sub::subsub::Def;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - sub (t)
        - sub::Def (t)
        - sub::subsub (t)
        "###);
    }

    #[test]
    fn type_reexport_cross_crate() {
        // Reexports need to be visible from a crate, even if the original crate exports the item
        // at a shorter path.
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::S;
            }
            //- /lib.rs crate:lib
            pub struct S;
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - m (t)
        - m::S (t)
        - m::S (v)
        lib:
        - S (t)
        - S (v)
        "###);
    }

    #[test]
    fn macro_reexport() {
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub mod m {
                pub use lib::pub_macro;
            }
            //- /lib.rs crate:lib
            #[macro_export]
            macro_rules! pub_macro {
                () => {};
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - m (t)
        - m::pub_macro (m)
        lib:
        - pub_macro (m)
        "###);
    }

    #[test]
    fn module_reexport() {
        // Reexporting modules from a dependency adds all contents to the import map.
        let map = import_map(
            r"
            //- /main.rs crate:main deps:lib
            pub use lib::module as reexported_module;
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        main:
        - reexported_module (t)
        - reexported_module::S (t)
        - reexported_module::S (v)
        lib:
        - module (t)
        - module::S (t)
        - module::S (v)
        "###);
    }

    #[test]
    fn cyclic_module_reexport() {
        // A cyclic reexport does not hang.
        let map = import_map(
            r"
            //- /lib.rs crate:lib
            pub mod module {
                pub struct S;
                pub use super::sub::*;
            }

            pub mod sub {
                pub use super::module;
            }
        ",
        );

        assert_snapshot!(map, @r###"
        lib:
        - module (t)
        - module::S (t)
        - module::S (v)
        - sub (t)
        "###);
    }

    #[test]
    fn private_macro() {
        let map = import_map(
            r"
            //- /lib.rs crate:lib
            macro_rules! private_macro {
                () => {};
            }
        ",
        );

        assert_snapshot!(map, @r###"
        lib:
        "###);
    }
}
<|endoftext|>


<commit_before>use rustc::lint::*;
use syntax::codemap::mk_sp;
use syntax::ast;
use utils::{differing_macro_contexts, in_macro, snippet_opt, span_note_and_lint};
use syntax::ptr::P;

/// **What it does:** Checks for use of the non-existent `=*`, `=!` and `=-` operators.
///
/// **Why is this bad?** This is either a typo of `*=`, `!=` or `-=` or confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
/// ```
declare_lint! {
    pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
    Warn,
    "suspicious formatting of `*=`, `-=` or `!=`"
}

/// **What it does:** Checks for formatting of `else if`. It lints if the `else`
/// and `if` are not on the same line or the `else` seems to be missing.
///
/// **Why is this bad?** This is probably some refactoring remnant, even if the
/// code is correct, it might look confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// if foo {
/// } if bar { // looks like an `else` is missing here
/// }
///
/// if foo {
/// } else
///
/// if bar { // this is the `else` block of the previous `if`, but should it be?
/// }
/// ```
declare_lint! {
    pub SUSPICIOUS_ELSE_FORMATTING,
    Warn,
    "suspicious formatting of `else if`"
}

/// **What it does:** Checks for possible missing comma in an array. It lints if
/// an array element is a binary operator expression and it lies on two lines.
///
/// **Why is this bad?** This could lead to unexpected results.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// let a = &[
///     -1, -2, -3 // <= no coma here
///     -4, -5, -6
/// ];
/// ```
declare_lint! {
    pub POSSIBLE_MISSING_COMMA,
    Warn,
    "possible missing comma in array"
}

#[derive(Copy,Clone)]
pub struct Formatting;

impl LintPass for Formatting {
    fn get_lints(&self) -> LintArray {
        lint_array![SUSPICIOUS_ASSIGNMENT_FORMATTING, SUSPICIOUS_ELSE_FORMATTING, POSSIBLE_MISSING_COMMA]
    }
}

impl EarlyLintPass for Formatting {
    fn check_block(&mut self, cx: &EarlyContext, block: &ast::Block) {
        for w in block.stmts.windows(2) {
            match (&w[0].node, &w[1].node) {
                (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Expr(ref second)) |
                (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Semi(ref second)) => {
                    check_consecutive_ifs(cx, first, second);
                },
                _ => (),
            }
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext, expr: &ast::Expr) {
        check_assign(cx, expr);
        check_else_if(cx, expr);
        check_array(cx, expr);
    }
}

/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
fn check_assign(cx: &EarlyContext, expr: &ast::Expr) {
    if let ast::ExprKind::Assign(ref lhs, ref rhs) = expr.node {
        if !differing_macro_contexts(lhs.span, rhs.span) && !in_macro(cx, lhs.span) {
            let eq_span = mk_sp(lhs.span.hi, rhs.span.lo);

            if let ast::ExprKind::Unary(op, ref sub_rhs) = rhs.node {
                if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
                    let op = ast::UnOp::to_string(op);
                    let eqop_span = mk_sp(lhs.span.hi, sub_rhs.span.lo);
                    if eq_snippet.ends_with('=') {
                        span_note_and_lint(cx,
                                           SUSPICIOUS_ASSIGNMENT_FORMATTING,
                                           eqop_span,
                                           &format!("this looks like you are trying to use `.. {op}= ..`, but you \
                                                     really are doing `.. = ({op} ..)`",
                                                    op = op),
                                           eqop_span,
                                           &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op));
                    }
                }
            }
        }
    }
}

/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else if`.
fn check_else_if(cx: &EarlyContext, expr: &ast::Expr) {
    if let Some((then, &Some(ref else_))) = unsugar_if(expr) {
        if unsugar_if(else_).is_some() && !differing_macro_contexts(then.span, else_.span) && !in_macro(cx, then.span) {
            // this will be a span from the closing ‘}’ of the “then” block (excluding) to the
            // “if” of the “else if” block (excluding)
            let else_span = mk_sp(then.span.hi, else_.span.lo);

            // the snippet should look like " else \n    " with maybe comments anywhere
            // it’s bad when there is a ‘\n’ after the “else”
            if let Some(else_snippet) = snippet_opt(cx, else_span) {
                let else_pos = else_snippet.find("else").expect("there must be a `else` here");

                if else_snippet[else_pos..].contains('\n') {
                    span_note_and_lint(cx,
                                       SUSPICIOUS_ELSE_FORMATTING,
                                       else_span,
                                       "this is an `else if` but the formatting might hide it",
                                       else_span,
                                       "to remove this lint, remove the `else` or remove the new line between `else` \
                                        and `if`");
                }
            }
        }
    }
}

/// Implementation of the `POSSIBLE_MISSING_COMMA_IN_ARRAY_FORMATTING` lint for array
fn check_array(cx: &EarlyContext, expr: &ast::Expr) {
    if let ast::ExprKind::Array(ref array) = expr.node {
        for element in array {
            if let ast::ExprKind::Binary(ref op, ref lhs, _) = element.node {
                let space_span = mk_sp(lhs.span.hi, op.span.lo);
                if let Some(space_snippet) = snippet_opt(cx, space_span) {
                    let lint_span = mk_sp(lhs.span.hi, lhs.span.hi);
                    if space_snippet.contains('\n') {
                        span_note_and_lint(cx,
                                           POSSIBLE_MISSING_COMMA_IN_ARRAY_FORMATTING,
                                           lint_span,
                                           "possibly missing a comma here",
                                           lint_span,
                                           "to remove this lint, add a comma or write the expr in a single line");
                    }
                }
            }
        }
    }
}

/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for consecutive ifs.
fn check_consecutive_ifs(cx: &EarlyContext, first: &ast::Expr, second: &ast::Expr) {
    if !differing_macro_contexts(first.span, second.span) && !in_macro(cx, first.span) &&
       unsugar_if(first).is_some() && unsugar_if(second).is_some() {
        // where the else would be
        let else_span = mk_sp(first.span.hi, second.span.lo);

        if let Some(else_snippet) = snippet_opt(cx, else_span) {
            if !else_snippet.contains('\n') {
                span_note_and_lint(cx,
                                   SUSPICIOUS_ELSE_FORMATTING,
                                   else_span,
                                   "this looks like an `else if` but the `else` is missing",
                                   else_span,
                                   "to remove this lint, add the missing `else` or add a new line before the second \
                                    `if`");
            }
        }
    }
}

/// Match `if` or `if let` expressions and return the `then` and `else` block.
fn unsugar_if(expr: &ast::Expr) -> Option<(&P<ast::Block>, &Option<P<ast::Expr>>)> {
    match expr.node {
        ast::ExprKind::If(_, ref then, ref else_) |
        ast::ExprKind::IfLet(_, _, ref then, ref else_) => Some((then, else_)),
        _ => None,
    }
}
<commit_msg>Consider expression with macro for possible missing comma lint<commit_after>use rustc::lint::*;
use syntax::codemap::mk_sp;
use syntax::ast;
use utils::{differing_macro_contexts, in_macro, snippet_opt, span_note_and_lint};
use syntax::ptr::P;

/// **What it does:** Checks for use of the non-existent `=*`, `=!` and `=-` operators.
///
/// **Why is this bad?** This is either a typo of `*=`, `!=` or `-=` or confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
/// ```
declare_lint! {
    pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
    Warn,
    "suspicious formatting of `*=`, `-=` or `!=`"
}

/// **What it does:** Checks for formatting of `else if`. It lints if the `else`
/// and `if` are not on the same line or the `else` seems to be missing.
///
/// **Why is this bad?** This is probably some refactoring remnant, even if the
/// code is correct, it might look confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// if foo {
/// } if bar { // looks like an `else` is missing here
/// }
///
/// if foo {
/// } else
///
/// if bar { // this is the `else` block of the previous `if`, but should it be?
/// }
/// ```
declare_lint! {
    pub SUSPICIOUS_ELSE_FORMATTING,
    Warn,
    "suspicious formatting of `else if`"
}

/// **What it does:** Checks for possible missing comma in an array. It lints if
/// an array element is a binary operator expression and it lies on two lines.
///
/// **Why is this bad?** This could lead to unexpected results.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,ignore
/// let a = &[
///     -1, -2, -3 // <= no coma here
///     -4, -5, -6
/// ];
/// ```
declare_lint! {
    pub POSSIBLE_MISSING_COMMA,
    Warn,
    "possible missing comma in array"
}

#[derive(Copy,Clone)]
pub struct Formatting;

impl LintPass for Formatting {
    fn get_lints(&self) -> LintArray {
        lint_array![SUSPICIOUS_ASSIGNMENT_FORMATTING, SUSPICIOUS_ELSE_FORMATTING, POSSIBLE_MISSING_COMMA]
    }
}

impl EarlyLintPass for Formatting {
    fn check_block(&mut self, cx: &EarlyContext, block: &ast::Block) {
        for w in block.stmts.windows(2) {
            match (&w[0].node, &w[1].node) {
                (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Expr(ref second)) |
                (&ast::StmtKind::Expr(ref first), &ast::StmtKind::Semi(ref second)) => {
                    check_consecutive_ifs(cx, first, second);
                },
                _ => (),
            }
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext, expr: &ast::Expr) {
        check_assign(cx, expr);
        check_else_if(cx, expr);
        check_array(cx, expr);
    }
}

/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
fn check_assign(cx: &EarlyContext, expr: &ast::Expr) {
    if let ast::ExprKind::Assign(ref lhs, ref rhs) = expr.node {
        if !differing_macro_contexts(lhs.span, rhs.span) && !in_macro(cx, lhs.span) {
            let eq_span = mk_sp(lhs.span.hi, rhs.span.lo);

            if let ast::ExprKind::Unary(op, ref sub_rhs) = rhs.node {
                if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
                    let op = ast::UnOp::to_string(op);
                    let eqop_span = mk_sp(lhs.span.hi, sub_rhs.span.lo);
                    if eq_snippet.ends_with('=') {
                        span_note_and_lint(cx,
                                           SUSPICIOUS_ASSIGNMENT_FORMATTING,
                                           eqop_span,
                                           &format!("this looks like you are trying to use `.. {op}= ..`, but you \
                                                     really are doing `.. = ({op} ..)`",
                                                    op = op),
                                           eqop_span,
                                           &format!("to remove this lint, use either `{op}=` or `= {op}`", op = op));
                    }
                }
            }
        }
    }
}

/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else if`.
fn check_else_if(cx: &EarlyContext, expr: &ast::Expr) {
    if let Some((then, &Some(ref else_))) = unsugar_if(expr) {
        if unsugar_if(else_).is_some() && !differing_macro_contexts(then.span, else_.span) && !in_macro(cx, then.span) {
            // this will be a span from the closing ‘}’ of the “then” block (excluding) to the
            // “if” of the “else if” block (excluding)
            let else_span = mk_sp(then.span.hi, else_.span.lo);

            // the snippet should look like " else \n    " with maybe comments anywhere
            // it’s bad when there is a ‘\n’ after the “else”
            if let Some(else_snippet) = snippet_opt(cx, else_span) {
                let else_pos = else_snippet.find("else").expect("there must be a `else` here");

                if else_snippet[else_pos..].contains('\n') {
                    span_note_and_lint(cx,
                                       SUSPICIOUS_ELSE_FORMATTING,
                                       else_span,
                                       "this is an `else if` but the formatting might hide it",
                                       else_span,
                                       "to remove this lint, remove the `else` or remove the new line between `else` \
                                        and `if`");
                }
            }
        }
    }
}

/// Implementation of the `POSSIBLE_MISSING_COMMA_IN_ARRAY_FORMATTING` lint for array
fn check_array(cx: &EarlyContext, expr: &ast::Expr) {
    if let ast::ExprKind::Array(ref array) = expr.node {
        for element in array {
            if let ast::ExprKind::Binary(ref op, ref lhs, _) = element.node {
                if !differing_macro_contexts(lhs.span, op.span) {
                    let space_span = mk_sp(lhs.span.hi, op.span.lo);
                    if let Some(space_snippet) = snippet_opt(cx, space_span) {
                        let lint_span = mk_sp(lhs.span.hi, lhs.span.hi);
                        if space_snippet.contains('\n') {
                            span_note_and_lint(cx,
                                               POSSIBLE_MISSING_COMMA,
                                               lint_span,
                                               "possibly missing a comma here",
                                               lint_span,
                                               "to remove this lint, add a comma or write the expr in a single line");
                        }
                    }
                }
            }
        }
    }
}

/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for consecutive ifs.
fn check_consecutive_ifs(cx: &EarlyContext, first: &ast::Expr, second: &ast::Expr) {
    if !differing_macro_contexts(first.span, second.span) && !in_macro(cx, first.span) &&
       unsugar_if(first).is_some() && unsugar_if(second).is_some() {
        // where the else would be
        let else_span = mk_sp(first.span.hi, second.span.lo);

        if let Some(else_snippet) = snippet_opt(cx, else_span) {
            if !else_snippet.contains('\n') {
                span_note_and_lint(cx,
                                   SUSPICIOUS_ELSE_FORMATTING,
                                   else_span,
                                   "this looks like an `else if` but the `else` is missing",
                                   else_span,
                                   "to remove this lint, add the missing `else` or add a new line before the second \
                                    `if`");
            }
        }
    }
}

/// Match `if` or `if let` expressions and return the `then` and `else` block.
fn unsugar_if(expr: &ast::Expr) -> Option<(&P<ast::Block>, &Option<P<ast::Expr>>)> {
    match expr.node {
        ast::ExprKind::If(_, ref then, ref else_) |
        ast::ExprKind::IfLet(_, _, ref then, ref else_) => Some((then, else_)),
        _ => None,
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Alexander Reece
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::io;
use inflections::Inflect;
use specs::{ClassMethod, ClassMethodField};

use CodeGenerator;
use common;
use common::domain::{Domain, DomainMapper};
use parsergen::FieldChunk;

type Field<'a> = common::Field<'a, ClassMethodField>;

pub struct MethodModuleWriter<'a> {
    module: &'a str,
    method: &'a ClassMethod,
    fields: Vec<Field<'a>>,
    has_lifetimes: bool,
}

impl<'a> MethodModuleWriter<'a> {
    pub fn new(module: &'a str, method: &'a ClassMethod, domain_mapper: &DomainMapper) -> Self {
        // fields
        let fields = method.fields().iter()
            .map(|field| {
                let ty = Domain::new(domain_mapper.map(field.domain()));
                Field::from_amqp0_field(field, ty)
            })
            .collect::<Vec<_>>();

        // has_lifetimes
        let has_lifetimes = fields.iter()
            .map(|f| !f.is_reserved() && !f.ty().is_copy())
            .any(|is_copy| is_copy);

        MethodModuleWriter {
            module: module,
            method: method,
            fields: fields,
            has_lifetimes: has_lifetimes,
        }
    }

    fn field_chunks(&self) -> Vec<FieldChunk> {
        let mut num_flags = 0;
        let field_chunks = Vec::new();
        self.fields.iter()
            .fold(field_chunks, |mut field_chunks, field| {
                let part_needs_adding = if let Domain::Bit = *field.ty() {
                    let needs_adding = field_chunks.last_mut()
                        .map(|flag| !flag.add_field(field))
                        .unwrap_or(true);

                    if needs_adding {
                        num_flags += 1;
                    }
                    needs_adding
                }
                    else {
                        true
                    };

                if part_needs_adding {
                    if let Domain::Bit = *field.ty() {
                        assert_ne!(0, num_flags);
                    }
                    field_chunks.push(FieldChunk::from_field(field, num_flags))
                }
                field_chunks
            })
    }
}

impl<'a> CodeGenerator for MethodModuleWriter<'a> {
    fn write_rust_to<W>(&self, writer: &mut W) -> io::Result<()>
        where W: io::Write
    {
        let struct_name = self.method.name().to_pascal_case();
        let lifetimes = if self.has_lifetimes { "<'a>" } else { "" };
        let uses_pool = self.fields.iter().any(|f| f.ty().is_owned());

        try!(write!(writer, "\n\
    impl<'a> ::NomBytes<'a> for ::primitives::{}::{}{} {{\n\
        type Output = Self;\n\
        fn nom_bytes<'b, P>(input: &'a [u8], {}: &'b mut P) -> IResult<&'a [u8], Self>\n\
            where P: ::pool::ParserPool\n\
        {{\n\
            do_parse!(input,\n",
            self.module,
            struct_name,
            lifetimes,
            if uses_pool { "pool" } else { "_" }
        ));

        let field_chunks = self.field_chunks();

        for chunk in &field_chunks {
            let nom_parser = chunk.nom_parser();
            if let Some(name) = chunk.capture_name() {
                try!(writeln!(writer, "{}: {} >>", name, nom_parser));
            }
            else {
                try!(writeln!(writer, "{} >>", nom_parser));
            }
        }

        let arguments = field_chunks.iter()
            .flat_map(|p| p.arg_names())
            .collect::<Vec<_>>()
            .join(", ");

        try!(writeln!(writer, "(::primitives::{}::{}::new({}))", self.module, struct_name, arguments));

        try!(writeln!(writer, ") // do_parse!"));
        try!(writeln!(writer, "}} // fn nom_bytes"));
        try!(writeln!(writer, "}} // impl NomBytes"));

        Ok(())
    }
}<commit_msg>[parsergen] Update if-else formatting in `MethodModuleWriter`<commit_after>// Copyright 2016 Alexander Reece
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::io;
use inflections::Inflect;
use specs::{ClassMethod, ClassMethodField};

use CodeGenerator;
use common;
use common::domain::{Domain, DomainMapper};
use parsergen::FieldChunk;

type Field<'a> = common::Field<'a, ClassMethodField>;

pub struct MethodModuleWriter<'a> {
    module: &'a str,
    method: &'a ClassMethod,
    fields: Vec<Field<'a>>,
    has_lifetimes: bool,
}

impl<'a> MethodModuleWriter<'a> {
    pub fn new(module: &'a str, method: &'a ClassMethod, domain_mapper: &DomainMapper) -> Self {
        // fields
        let fields = method.fields().iter()
            .map(|field| {
                let ty = Domain::new(domain_mapper.map(field.domain()));
                Field::from_amqp0_field(field, ty)
            })
            .collect::<Vec<_>>();

        // has_lifetimes
        let has_lifetimes = fields.iter()
            .map(|f| !f.is_reserved() && !f.ty().is_copy())
            .any(|is_copy| is_copy);

        MethodModuleWriter {
            module: module,
            method: method,
            fields: fields,
            has_lifetimes: has_lifetimes,
        }
    }

    fn field_chunks(&self) -> Vec<FieldChunk> {
        let mut num_flags = 0;
        let field_chunks = Vec::new();
        self.fields.iter()
            .fold(field_chunks, |mut field_chunks, field| {
                let part_needs_adding = if let Domain::Bit = *field.ty() {
                    let needs_adding = field_chunks.last_mut()
                        .map(|flag| !flag.add_field(field))
                        .unwrap_or(true);

                    if needs_adding {
                        num_flags += 1;
                    }
                    needs_adding
                } else {
                    true
                };

                if part_needs_adding {
                    if let Domain::Bit = *field.ty() {
                        assert_ne!(0, num_flags);
                    }
                    field_chunks.push(FieldChunk::from_field(field, num_flags))
                }
                field_chunks
            })
    }
}

impl<'a> CodeGenerator for MethodModuleWriter<'a> {
    fn write_rust_to<W>(&self, writer: &mut W) -> io::Result<()>
        where W: io::Write
    {
        let struct_name = self.method.name().to_pascal_case();
        let lifetimes = if self.has_lifetimes { "<'a>" } else { "" };
        let uses_pool = self.fields.iter().any(|f| f.ty().is_owned());

        try!(write!(writer, "\n\
    impl<'a> ::NomBytes<'a> for ::primitives::{}::{}{} {{\n\
        type Output = Self;\n\
        fn nom_bytes<'b, P>(input: &'a [u8], {}: &'b mut P) -> IResult<&'a [u8], Self>\n\
            where P: ::pool::ParserPool\n\
        {{\n\
            do_parse!(input,\n",
            self.module,
            struct_name,
            lifetimes,
            if uses_pool { "pool" } else { "_" }
        ));

        let field_chunks = self.field_chunks();

        for chunk in &field_chunks {
            let nom_parser = chunk.nom_parser();
            if let Some(name) = chunk.capture_name() {
                try!(writeln!(writer, "{}: {} >>", name, nom_parser));
            }
            else {
                try!(writeln!(writer, "{} >>", nom_parser));
            }
        }

        let arguments = field_chunks.iter()
            .flat_map(|p| p.arg_names())
            .collect::<Vec<_>>()
            .join(", ");

        try!(writeln!(writer, "(::primitives::{}::{}::new({}))", self.module, struct_name, arguments));

        try!(writeln!(writer, ") // do_parse!"));
        try!(writeln!(writer, "}} // fn nom_bytes"));
        try!(writeln!(writer, "}} // impl NomBytes"));

        Ok(())
    }
}<|endoftext|>