<commit_before>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::net::SocketAddr;
use std::ffi::OsStr;
use std::path::Path;

use hyper::StatusCode;
use tempfile;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::Backend;
use error::Error;
use utils::{
    read,
    read_bytes,
    find_cmd,
};
use http_utils::{
    SimpleServer,
    response_from_status,
    response_from_data
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};
use cmd_test::TEST_RUNNER;

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        __cargo_web.target = "{{{ target }}}";
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
    <script src="/__cargo-web__/test_runner.js"></script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    backend: Backend,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let possible_commands =
        if cfg!( windows ) {
            &[ "chrome.exe" ][..]
        } else {
            &[ "chromium", "chromium-browser", "google-chrome", "google-chrome-stable", "Google Chrome" ][..]
        };

    let chromium_executable = find_cmd( possible_commands )
        .or_else( || {
            let path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
            if Path::new( path ).exists() {
                Some( path )
            } else {
                None
            }
        })
        .ok_or_else( || {
            Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() )
        })?;

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data: BTreeMap< &str, Value > = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough.into() );
    template_data.insert( "target", backend.triplet().into() );
    let test_index = handlebars.render_template( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let (addr_tx, addr_rx) = channel();
    thread::spawn( move || {
        let server = SimpleServer::new(&"127.0.0.1:0".parse().unwrap(), move |request| {
            let path = request.uri().path();
            if path == "/" || path == "index.html" {
                response_from_data( &"text/html".parse().unwrap(), test_index.clone().into_bytes() )
            } else if path == "/js/app.js" {
                let data = server_app_js.lock().unwrap().clone();
                response_from_data( &"application/javascript".parse().unwrap(), data.into_bytes() )
            } else if path == "/__cargo-web__/test_runner.js" {
                response_from_data(
                    &"application/javascript".parse().unwrap(),
                    TEST_RUNNER.as_bytes().to_vec() )
            } else {
                match *server_wasm_url.lock().unwrap() {
                    Some( ref server_wasm_url ) if path == *server_wasm_url => {
                        let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                        response_from_data( &"application/wasm".parse().unwrap(), data )
                    },
                    _ => response_from_status(StatusCode::NOT_FOUND)
                }
            }
        });
        addr_tx.send(server.server_addr()).unwrap();
        server.run();
    });
    let server_address: SocketAddr = addr_rx.recv().unwrap();

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if backend.is_any_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = tempfile::Builder::new().prefix( "cargo-web-chromium-profile" ).tempdir().unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--no-sandbox" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 60 );
    let mut get_status_req = None;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    eprintln!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                eprintln!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        eprintln!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    eprintln!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if backend.is_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        eprintln!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<commit_msg>Increase Chromium testing timeout and add a warning after 2 minutes (#212)<commit_after>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::net::SocketAddr;
use std::ffi::OsStr;
use std::path::Path;

use hyper::StatusCode;
use tempfile;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::Backend;
use error::Error;
use utils::{
    read,
    read_bytes,
    find_cmd,
};
use http_utils::{
    SimpleServer,
    response_from_status,
    response_from_data
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};
use cmd_test::TEST_RUNNER;

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        __cargo_web.target = "{{{ target }}}";
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
    <script src="/__cargo-web__/test_runner.js"></script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    backend: Backend,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let possible_commands =
        if cfg!( windows ) {
            &[ "chrome.exe" ][..]
        } else {
            &[ "chromium", "chromium-browser", "google-chrome", "google-chrome-stable", "Google Chrome" ][..]
        };

    let chromium_executable = find_cmd( possible_commands )
        .or_else( || {
            let path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
            if Path::new( path ).exists() {
                Some( path )
            } else {
                None
            }
        })
        .ok_or_else( || {
            Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() )
        })?;

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data: BTreeMap< &str, Value > = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough.into() );
    template_data.insert( "target", backend.triplet().into() );
    let test_index = handlebars.render_template( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let (addr_tx, addr_rx) = channel();
    thread::spawn( move || {
        let server = SimpleServer::new(&"127.0.0.1:0".parse().unwrap(), move |request| {
            let path = request.uri().path();
            if path == "/" || path == "index.html" {
                response_from_data( &"text/html".parse().unwrap(), test_index.clone().into_bytes() )
            } else if path == "/js/app.js" {
                let data = server_app_js.lock().unwrap().clone();
                response_from_data( &"application/javascript".parse().unwrap(), data.into_bytes() )
            } else if path == "/__cargo-web__/test_runner.js" {
                response_from_data(
                    &"application/javascript".parse().unwrap(),
                    TEST_RUNNER.as_bytes().to_vec() )
            } else {
                match *server_wasm_url.lock().unwrap() {
                    Some( ref server_wasm_url ) if path == *server_wasm_url => {
                        let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                        response_from_data( &"application/wasm".parse().unwrap(), data )
                    },
                    _ => response_from_status(StatusCode::NOT_FOUND)
                }
            }
        });
        addr_tx.send(server.server_addr()).unwrap();
        server.run();
    });
    let server_address: SocketAddr = addr_rx.recv().unwrap();

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if backend.is_any_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = tempfile::Builder::new().prefix( "cargo-web-chromium-profile" ).tempdir().unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--no-sandbox" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 600 );
    let mut get_status_req = None;
    let mut warned = false;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;
        if elapsed >= Duration::from_secs(120) && !warned {
            eprintln!( "The tests have been running for over 2 minutes" );
            warned = true;
        }

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    eprintln!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                eprintln!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        eprintln!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    eprintln!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if backend.is_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        eprintln!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<|endoftext|>


<commit_before>use std::sync::mpsc::{Sender, Receiver};
use std::time::Duration;
use std::io::timer;
use std::cmp;

use game::Color;
use uci;

use super::types::Data;

pub fn start(data: Data, c: Color, tx: Sender<uci::Cmd>, rx_kill: Receiver<()>) {
    match data {
        Data::Infinite => return,
        Data::Exact(val) => {
            timer::sleep(val);
            let _ = tx.send(uci::Cmd::Stop);
        },
        Data::Remain(val) => {
            let base = val.time(c).unwrap_or(Duration::zero());
            let inc = val.inc(c).unwrap_or(Duration::zero());
            timer::sleep(calc_time(base, inc));
            // TODO terminate when received from rx_kill
            let _ = tx.send(uci::Cmd::Stop);
        },
    }
}

fn calc_time(base: Duration, inc: Duration) -> Duration {
    cmp::min( base / 40 + inc, base )
}
<commit_msg>Implement timer termination<commit_after>use std::sync::mpsc::{Sender, Receiver};
use std::time::Duration;
use std::io::Timer;
use std::cmp;

use game::Color;
use uci;

use super::types::Data;

pub fn start(data: Data, c: Color, tx: Sender<uci::Cmd>, rx_kill: Receiver<()>) {
    match data {
        Data::Infinite => return,
        Data::Exact(val) => {
            let mut timer = Timer::new().unwrap();
            let rx_timer = timer.oneshot(val);
            select!(
                _ = rx_timer.recv() => { let _ = tx.send(uci::Cmd::Stop); },
                _ = rx_kill.recv() => {}
            )
        },
        Data::Remain(val) => {
            let base = val.time(c).unwrap_or(Duration::zero());
            let inc = val.inc(c).unwrap_or(Duration::zero());
            let val = calc_time(base, inc);
            let mut timer = Timer::new().unwrap();
            let rx_timer = timer.oneshot(val);
            select!(
                _ = rx_timer.recv() => { let _ = tx.send(uci::Cmd::Stop); },
                _ = rx_kill.recv() => {}
            )
        },
    }
}

fn calc_time(base: Duration, inc: Duration) -> Duration {
    cmp::min( base / 40 + inc, base )
}
<|endoftext|>


<commit_before>use std::ops::DerefMut;
use std::result;
use std::vec::Vec;

use crate::common::SectionId;
use crate::write::{
    DebugAbbrev, DebugInfo, DebugInfoReference, DebugLine, DebugLineStr, DebugLoc, DebugLocLists,
    DebugRanges, DebugRngLists, DebugStr, Writer,
};

macro_rules! define_section {
    ($name:ident, $offset:ident, $docs:expr) => {
        #[doc=$docs]
        #[derive(Debug, Default)]
        pub struct $name<W: Writer>(pub W);

        impl<W: Writer> $name<W> {
            /// Return the offset of the next write.
            pub fn offset(&self) -> $offset {
                $offset(self.len())
            }
        }

        impl<W: Writer> From<W> for $name<W> {
            #[inline]
            fn from(w: W) -> Self {
                $name(w)
            }
        }

        impl<W: Writer> Deref for $name<W> {
            type Target = W;

            #[inline]
            fn deref(&self) -> &W {
                &self.0
            }
        }

        impl<W: Writer> DerefMut for $name<W> {
            #[inline]
            fn deref_mut(&mut self) -> &mut W {
                &mut self.0
            }
        }

        impl<W: Writer> Section<W> for $name<W> {
            #[inline]
            fn id(&self) -> SectionId {
                SectionId::$name
            }
        }
    };
}

/// Functionality common to all writable DWARF sections.
pub trait Section<W: Writer>: DerefMut<Target = W> {
    /// Returns the DWARF section kind for this type.
    fn id(&self) -> SectionId;

    /// Returns the ELF section name for this type.
    fn name(&self) -> &'static str {
        self.id().name()
    }
}

/// All of the writable DWARF sections.
#[derive(Debug, Default)]
pub struct Sections<W: Writer> {
    /// The `.debug_abbrev` section.
    pub debug_abbrev: DebugAbbrev<W>,
    /// The `.debug_info` section.
    pub debug_info: DebugInfo<W>,
    /// The `.debug_line` section.
    pub debug_line: DebugLine<W>,
    /// The `.debug_line_str` section.
    pub debug_line_str: DebugLineStr<W>,
    /// The `.debug_ranges` section.
    pub debug_ranges: DebugRanges<W>,
    /// The `.debug_rnglists` section.
    pub debug_rnglists: DebugRngLists<W>,
    /// The `.debug_loc` section.
    pub debug_loc: DebugLoc<W>,
    /// The `.debug_loclists` section.
    pub debug_loclists: DebugLocLists<W>,
    /// The `.debug_str` section.
    pub debug_str: DebugStr<W>,
    /// Unresolved references in the `.debug_info` section.
    pub(crate) debug_info_refs: Vec<DebugInfoReference>,
    /// Unresolved references in the `.debug_loc` section.
    pub(crate) debug_loc_refs: Vec<DebugInfoReference>,
    /// Unresolved references in the `.debug_loclists` section.
    pub(crate) debug_loclists_refs: Vec<DebugInfoReference>,
}

impl<W: Writer + Clone> Sections<W> {
    /// Create a new `Sections` using clones of the given `section`.
    pub fn new(section: W) -> Self {
        Sections {
            debug_abbrev: DebugAbbrev(section.clone()),
            debug_info: DebugInfo(section.clone()),
            debug_line: DebugLine(section.clone()),
            debug_line_str: DebugLineStr(section.clone()),
            debug_ranges: DebugRanges(section.clone()),
            debug_rnglists: DebugRngLists(section.clone()),
            debug_loc: DebugLoc(section.clone()),
            debug_loclists: DebugLocLists(section.clone()),
            debug_str: DebugStr(section.clone()),
            debug_info_refs: Vec::new(),
            debug_loc_refs: Vec::new(),
            debug_loclists_refs: Vec::new(),
        }
    }
}

impl<W: Writer> Sections<W> {
    /// For each section, call `f` once with a shared reference.
    pub fn for_each<F, E>(&self, mut f: F) -> result::Result<(), E>
    where
        F: FnMut(SectionId, &W) -> result::Result<(), E>,
    {
        macro_rules! f {
            ($s:expr) => {
                f($s.id(), &$s)
            };
        }
        // Ordered so that earlier sections do not reference later sections.
        f!(self.debug_abbrev)?;
        f!(self.debug_str)?;
        f!(self.debug_line_str)?;
        f!(self.debug_line)?;
        f!(self.debug_ranges)?;
        f!(self.debug_rnglists)?;
        f!(self.debug_loc)?;
        f!(self.debug_loclists)?;
        f!(self.debug_info)?;
        Ok(())
    }

    /// For each section, call `f` once with a mutable reference.
    pub fn for_each_mut<F, E>(&mut self, mut f: F) -> result::Result<(), E>
    where
        F: FnMut(SectionId, &mut W) -> result::Result<(), E>,
    {
        macro_rules! f {
            ($s:expr) => {
                f($s.id(), &mut $s)
            };
        }
        // Ordered so that earlier sections do not reference later sections.
        f!(self.debug_abbrev)?;
        f!(self.debug_str)?;
        f!(self.debug_line_str)?;
        f!(self.debug_line)?;
        f!(self.debug_ranges)?;
        f!(self.debug_rnglists)?;
        f!(self.debug_loc)?;
        f!(self.debug_loclists)?;
        f!(self.debug_info)?;
        Ok(())
    }
}
<commit_msg>write: add EhFrame and DebugFrame to sections<commit_after>use std::ops::DerefMut;
use std::result;
use std::vec::Vec;

use crate::common::SectionId;
use crate::write::{
    DebugAbbrev, DebugFrame, DebugInfo, DebugInfoReference, DebugLine, DebugLineStr, DebugLoc,
    DebugLocLists, DebugRanges, DebugRngLists, DebugStr, EhFrame, Writer,
};

macro_rules! define_section {
    ($name:ident, $offset:ident, $docs:expr) => {
        #[doc=$docs]
        #[derive(Debug, Default)]
        pub struct $name<W: Writer>(pub W);

        impl<W: Writer> $name<W> {
            /// Return the offset of the next write.
            pub fn offset(&self) -> $offset {
                $offset(self.len())
            }
        }

        impl<W: Writer> From<W> for $name<W> {
            #[inline]
            fn from(w: W) -> Self {
                $name(w)
            }
        }

        impl<W: Writer> Deref for $name<W> {
            type Target = W;

            #[inline]
            fn deref(&self) -> &W {
                &self.0
            }
        }

        impl<W: Writer> DerefMut for $name<W> {
            #[inline]
            fn deref_mut(&mut self) -> &mut W {
                &mut self.0
            }
        }

        impl<W: Writer> Section<W> for $name<W> {
            #[inline]
            fn id(&self) -> SectionId {
                SectionId::$name
            }
        }
    };
}

/// Functionality common to all writable DWARF sections.
pub trait Section<W: Writer>: DerefMut<Target = W> {
    /// Returns the DWARF section kind for this type.
    fn id(&self) -> SectionId;

    /// Returns the ELF section name for this type.
    fn name(&self) -> &'static str {
        self.id().name()
    }
}

/// All of the writable DWARF sections.
#[derive(Debug, Default)]
pub struct Sections<W: Writer> {
    /// The `.debug_abbrev` section.
    pub debug_abbrev: DebugAbbrev<W>,
    /// The `.debug_info` section.
    pub debug_info: DebugInfo<W>,
    /// The `.debug_line` section.
    pub debug_line: DebugLine<W>,
    /// The `.debug_line_str` section.
    pub debug_line_str: DebugLineStr<W>,
    /// The `.debug_ranges` section.
    pub debug_ranges: DebugRanges<W>,
    /// The `.debug_rnglists` section.
    pub debug_rnglists: DebugRngLists<W>,
    /// The `.debug_loc` section.
    pub debug_loc: DebugLoc<W>,
    /// The `.debug_loclists` section.
    pub debug_loclists: DebugLocLists<W>,
    /// The `.debug_str` section.
    pub debug_str: DebugStr<W>,
    /// The `.debug_frame` section.
    pub debug_frame: DebugFrame<W>,
    /// The `.eh_frame` section.
    pub eh_frame: EhFrame<W>,
    /// Unresolved references in the `.debug_info` section.
    pub(crate) debug_info_refs: Vec<DebugInfoReference>,
    /// Unresolved references in the `.debug_loc` section.
    pub(crate) debug_loc_refs: Vec<DebugInfoReference>,
    /// Unresolved references in the `.debug_loclists` section.
    pub(crate) debug_loclists_refs: Vec<DebugInfoReference>,
}

impl<W: Writer + Clone> Sections<W> {
    /// Create a new `Sections` using clones of the given `section`.
    pub fn new(section: W) -> Self {
        Sections {
            debug_abbrev: DebugAbbrev(section.clone()),
            debug_info: DebugInfo(section.clone()),
            debug_line: DebugLine(section.clone()),
            debug_line_str: DebugLineStr(section.clone()),
            debug_ranges: DebugRanges(section.clone()),
            debug_rnglists: DebugRngLists(section.clone()),
            debug_loc: DebugLoc(section.clone()),
            debug_loclists: DebugLocLists(section.clone()),
            debug_str: DebugStr(section.clone()),
            debug_frame: DebugFrame(section.clone()),
            eh_frame: EhFrame(section.clone()),
            debug_info_refs: Vec::new(),
            debug_loc_refs: Vec::new(),
            debug_loclists_refs: Vec::new(),
        }
    }
}

impl<W: Writer> Sections<W> {
    /// For each section, call `f` once with a shared reference.
    pub fn for_each<F, E>(&self, mut f: F) -> result::Result<(), E>
    where
        F: FnMut(SectionId, &W) -> result::Result<(), E>,
    {
        macro_rules! f {
            ($s:expr) => {
                f($s.id(), &$s)
            };
        }
        // Ordered so that earlier sections do not reference later sections.
        f!(self.debug_abbrev)?;
        f!(self.debug_str)?;
        f!(self.debug_line_str)?;
        f!(self.debug_line)?;
        f!(self.debug_ranges)?;
        f!(self.debug_rnglists)?;
        f!(self.debug_loc)?;
        f!(self.debug_loclists)?;
        f!(self.debug_info)?;
        f!(self.debug_frame)?;
        f!(self.eh_frame)?;
        Ok(())
    }

    /// For each section, call `f` once with a mutable reference.
    pub fn for_each_mut<F, E>(&mut self, mut f: F) -> result::Result<(), E>
    where
        F: FnMut(SectionId, &mut W) -> result::Result<(), E>,
    {
        macro_rules! f {
            ($s:expr) => {
                f($s.id(), &mut $s)
            };
        }
        // Ordered so that earlier sections do not reference later sections.
        f!(self.debug_abbrev)?;
        f!(self.debug_str)?;
        f!(self.debug_line_str)?;
        f!(self.debug_line)?;
        f!(self.debug_ranges)?;
        f!(self.debug_rnglists)?;
        f!(self.debug_loc)?;
        f!(self.debug_loclists)?;
        f!(self.debug_info)?;
        f!(self.debug_frame)?;
        f!(self.eh_frame)?;
        Ok(())
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::node_identifier::NodeIdentifier;
use crate::util::{is_relay_custom_inline_fragment_directive, PointerAddress};

use dashmap::DashMap;
use fnv::FnvBuildHasher;
use graphql_ir::{
    Condition, FragmentDefinition, InlineFragment, LinkedField, OperationDefinition, Program,
    Selection, Transformed, TransformedValue,
};
use im::HashMap;
use rayon::prelude::*;
use schema::Schema;
use std::iter::Iterator;
use std::sync::Arc;

/**
 * A transform that removes redundant fields and fragment spreads. Redundancy is
 * defined in this context as any selection that is guaranteed to already be
 * fetched by an ancestor selection. This can occur in two cases:
 *
 * 1. Simple duplicates at the same level of the document can always be skipped:
 *
 *
 * fragment Foo on FooType {
 *   id
 *   id
 *   ...Bar
 *   ...Bar
 * }
 *
 *
 * Becomes
 *
 *
 * fragment Foo on FooType {
 *   id
 *   ...Bar
 * }
 *
 *
 * 2. Inline fragments and conditions introduce the possibility for duplication
 * at different levels of the tree. Whenever a selection is fetched in a parent,
 * it is redundant to also fetch it in a child:
 *
 *
 * fragment Foo on FooType {
 *   id
 *   ... on OtherType {
 *     id # 1
 *   }
 *   ... on FooType @include(if: $cond) {
 *     id # 2
 *   }
 * }
 *
 *
 * Becomes:
 *
 *
 * fragment Foo on FooType {
 *   id
 * }
 *
 *
 * In this example:
 * - 1 can be skipped because `id` is already fetched by the parent. Even
 *   though the type is different (FooType/OtherType), the inline fragment
 *   cannot match without the outer fragment matching so the outer `id` is
 *   guaranteed to already be fetched.
 * - 2 can be skipped for similar reasons: it doesn't matter if the condition
 *   holds, `id` is already fetched by the parent regardless.
 *
 * This transform also handles more complicated cases in which selections are
 * nested:
 *
 *
 * fragment Foo on FooType {
 *   a {
 *     bb
 *   }
 *   ... on OtherType {
 *     a {
 *       bb # 1
 *       cc
 *     }
 *   }
 *  }
 *
 *
 * Becomes
 *
 *
 * fragment Foo on FooType {
 *   a {
 *     bb
 *   }
 *   ... on OtherType {
 *     a {
 *       cc
 *     }
 *   }
 *  }
 *
 *
 * 1 can be skipped because it is already fetched at the outer level.
 */
pub fn skip_redundant_nodes(program: &Program) -> Program {
    let transform = SkipRedundantNodesTransform::new(program);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

#[derive(Default, Clone)]
struct SelectionMap(HashMap<NodeIdentifier, Option<SelectionMap>, FnvBuildHasher>);

type Cache = DashMap<PointerAddress, (Transformed<Selection>, SelectionMap)>;

struct SkipRedundantNodesTransform {
    schema: Arc<Schema>,
    cache: Cache,
}

impl<'s> SkipRedundantNodesTransform {
    fn new(program: &'_ Program) -> Self {
        Self {
            schema: Arc::clone(&program.schema),
            cache: DashMap::new(),
        }
    }

    fn transform_selection(
        &self,
        selection: &Selection,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Selection> {
        // This will optimize a traversal of the same subselections.
        // If it's the same node, and selection_map is empty
        // result of transform_selection has to be the same.
        let is_empty = selection_map.0.is_empty();
        if is_empty {
            let key = PointerAddress::new(selection);
            if let Some(cached) = self.cache.get(&key) {
                let (cached_result, cached_selection_map) = cached.clone();
                *selection_map = cached_selection_map;
                return cached_result;
            }
        }
        let identifier = NodeIdentifier::from_selection(&self.schema, selection);
        let result = match selection {
            Selection::ScalarField(_) => {
                if selection_map.0.contains_key(&identifier) {
                    Transformed::Delete
                } else {
                    selection_map.0.insert(identifier, None);
                    Transformed::Keep
                }
            }
            Selection::FragmentSpread(_) => {
                if selection_map.0.contains_key(&identifier) {
                    Transformed::Delete
                } else {
                    selection_map.0.insert(identifier, None);
                    Transformed::Keep
                }
            }
            Selection::LinkedField(selection) => {
                if let Some(Some(linked_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_linked_field(selection, linked_selection_map)
                        .map(Selection::LinkedField)
                } else {
                    let mut linked_selection_map = Default::default();
                    let result = self
                        .transform_linked_field(selection, &mut linked_selection_map)
                        .map(Selection::LinkedField);
                    if !linked_selection_map.0.is_empty() {
                        selection_map
                            .0
                            .insert(identifier, Some(linked_selection_map));
                    }
                    result
                }
            }
            Selection::Condition(selection) => {
                if let Some(Some(existing_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_condition(selection, existing_selection_map)
                        .map(Selection::Condition)
                } else {
                    // Fork the selection map to prevent conditional selections from
                    // affecting the outer "guaranteed" selections.
                    let mut next_selection_map = selection_map.clone();
                    let result = self
                        .transform_condition(selection, &mut next_selection_map)
                        .map(Selection::Condition);
                    selection_map.0.insert(identifier, Some(next_selection_map));
                    result
                }
            }
            Selection::InlineFragment(selection) => {
                if let Some(Some(existing_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_inline_fragment(selection, existing_selection_map)
                        .map(Selection::InlineFragment)
                } else if selection
                    .directives
                    .iter()
                    .any(is_relay_custom_inline_fragment_directive)
                {
                    let mut linked_selection_map = Default::default();
                    let result = self
                        .transform_inline_fragment(selection, &mut linked_selection_map)
                        .map(Selection::InlineFragment);
                    if !linked_selection_map.0.is_empty() {
                        selection_map
                            .0
                            .insert(identifier, Some(linked_selection_map));
                    }
                    result
                } else {
                    // Fork for inline fragments for the same reason
                    let mut next_selection_map = selection_map.clone();
                    let result = self
                        .transform_inline_fragment(selection, &mut next_selection_map)
                        .map(Selection::InlineFragment);
                    selection_map.0.insert(identifier, Some(next_selection_map));
                    result
                }
            }
        };
        if is_empty {
            let key = PointerAddress::new(selection);
            self.cache
                .insert(key, (result.clone(), selection_map.clone()));
        }
        result
    }

    fn transform_linked_field(
        &self,
        field: &LinkedField,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<LinkedField>> {
        let selections = self.transform_selections(&field.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(LinkedField {
                        selections,
                        ..field.clone()
                    }))
                }
            }
        }
    }

    fn transform_condition(
        &self,
        condition: &Condition,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<Condition>> {
        let selections = self.transform_selections(&condition.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(Condition {
                        selections,
                        ..condition.clone()
                    }))
                }
            }
        }
    }

    fn transform_inline_fragment(
        &self,
        fragment: &InlineFragment,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<InlineFragment>> {
        let selections = self.transform_selections(&fragment.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(InlineFragment {
                        selections,
                        ..fragment.clone()
                    }))
                }
            }
        }
    }

    // Mostly a copy from Transformer::transform_list, but does partition and pass down `selection_map`.
    fn transform_selections(
        &self,
        selections: &[Selection],
        selection_map: &mut SelectionMap,
    ) -> TransformedValue<Vec<Selection>> {
        if selections.is_empty() {
            return TransformedValue::Keep;
        }
        let mut result: Vec<Selection> = Vec::new();
        let mut has_changes = false;
        let partitioned = get_partitioned_selections(selections);
        let selections = match partitioned {
            Some(ref partitioned) => partitioned.as_slice(),
            _ => selections,
        };

        for (index, prev_item) in selections.iter().enumerate() {
            let next_item = self.transform_selection(prev_item, selection_map);
            match next_item {
                Transformed::Keep => {
                    if has_changes {
                        result.push((*prev_item).clone());
                    }
                }
                Transformed::Delete => {
                    if !has_changes {
                        debug_assert!(result.capacity() == 0);
                        // assume most items won't be skipped and allocate space for all items
                        result.reserve(selections.len());
                        result.extend(selections.iter().take(index).cloned());
                        has_changes = true;
                    }
                }
                Transformed::Replace(next_item) => {
                    if !has_changes {
                        debug_assert!(result.capacity() == 0);
                        // assume most items won't be skipped and allocate space for all items
                        result.reserve(selections.len());
                        result.extend(selections.iter().take(index).cloned());
                        has_changes = true;
                    }
                    result.push(next_item);
                }
            }
        }
        if has_changes {
            TransformedValue::Replace(result)
        } else {
            match partitioned {
                Some(partitioned) => TransformedValue::Replace(partitioned),
                _ => TransformedValue::Keep,
            }
        }
    }

    fn transform_operation(
        &self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        let mut selection_map = Default::default();
        let selections = self.transform_selections(&operation.selections, &mut selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => Transformed::Replace(OperationDefinition {
                selections,
                ..operation.clone()
            }),
        }
    }

    fn transform_fragment(&self, fragment: &FragmentDefinition) -> Transformed<FragmentDefinition> {
        let mut selection_map = Default::default();
        let selections = self.transform_selections(&fragment.selections, &mut selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => Transformed::Replace(FragmentDefinition {
                selections,
                ..fragment.clone()
            }),
        }
    }

    fn transform_program(&self, program: &Program) -> TransformedValue<Program> {
        let operations: Vec<Arc<OperationDefinition>> = program
            .par_operations()
            .filter_map(|operation| match self.transform_operation(operation) {
                Transformed::Delete => None,
                Transformed::Keep => Some(Arc::clone(operation)),
                Transformed::Replace(replacement) => Some(Arc::new(replacement)),
            })
            .collect();
        let fragments: Vec<Arc<FragmentDefinition>> = program
            .par_fragments()
            .filter_map(|fragment| match self.transform_fragment(fragment) {
                Transformed::Delete => None,
                Transformed::Keep => Some(Arc::clone(fragment)),
                Transformed::Replace(replacement) => Some(Arc::new(replacement)),
            })
            .collect();
        let mut next_program = Program::new(Arc::clone(&program.schema));
        for operation in operations {
            next_program.insert_operation(operation);
        }
        for fragment in fragments {
            next_program.insert_fragment(fragment);
        }
        TransformedValue::Replace(next_program)
    }
}

/* Selections are sorted with fields first, "conditionals"
 * (inline fragments & conditions) last. This means that all fields that are
 * guaranteed to be fetched are encountered prior to any duplicates that may be
 * fetched within a conditional.
 */
fn get_partitioned_selections(selections: &[Selection]) -> Option<Vec<Selection>> {
    let (mut left, right): (Vec<_>, Vec<_>) =
        selections.iter().cloned().partition(|sel| match sel {
            Selection::LinkedField(_) | Selection::ScalarField(_) => true,
            _ => false,
        });
    if left.len() == selections.len() {
        return None;
    }
    left.extend(right);
    Some(left)
}
<commit_msg>partially backout skip_redundant_nodes optimization<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::node_identifier::NodeIdentifier;
use crate::util::{is_relay_custom_inline_fragment_directive, PointerAddress};

use dashmap::DashMap;
use fnv::FnvBuildHasher;
use graphql_ir::{
    Condition, FragmentDefinition, InlineFragment, LinkedField, OperationDefinition, Program,
    Selection, Transformed, TransformedValue,
};
use im::HashMap;
use rayon::prelude::*;
use schema::Schema;
use std::iter::Iterator;
use std::sync::Arc;

/**
 * A transform that removes redundant fields and fragment spreads. Redundancy is
 * defined in this context as any selection that is guaranteed to already be
 * fetched by an ancestor selection. This can occur in two cases:
 *
 * 1. Simple duplicates at the same level of the document can always be skipped:
 *
 *
 * fragment Foo on FooType {
 *   id
 *   id
 *   ...Bar
 *   ...Bar
 * }
 *
 *
 * Becomes
 *
 *
 * fragment Foo on FooType {
 *   id
 *   ...Bar
 * }
 *
 *
 * 2. Inline fragments and conditions introduce the possibility for duplication
 * at different levels of the tree. Whenever a selection is fetched in a parent,
 * it is redundant to also fetch it in a child:
 *
 *
 * fragment Foo on FooType {
 *   id
 *   ... on OtherType {
 *     id # 1
 *   }
 *   ... on FooType @include(if: $cond) {
 *     id # 2
 *   }
 * }
 *
 *
 * Becomes:
 *
 *
 * fragment Foo on FooType {
 *   id
 * }
 *
 *
 * In this example:
 * - 1 can be skipped because `id` is already fetched by the parent. Even
 *   though the type is different (FooType/OtherType), the inline fragment
 *   cannot match without the outer fragment matching so the outer `id` is
 *   guaranteed to already be fetched.
 * - 2 can be skipped for similar reasons: it doesn't matter if the condition
 *   holds, `id` is already fetched by the parent regardless.
 *
 * This transform also handles more complicated cases in which selections are
 * nested:
 *
 *
 * fragment Foo on FooType {
 *   a {
 *     bb
 *   }
 *   ... on OtherType {
 *     a {
 *       bb # 1
 *       cc
 *     }
 *   }
 *  }
 *
 *
 * Becomes
 *
 *
 * fragment Foo on FooType {
 *   a {
 *     bb
 *   }
 *   ... on OtherType {
 *     a {
 *       cc
 *     }
 *   }
 *  }
 *
 *
 * 1 can be skipped because it is already fetched at the outer level.
 */
pub fn skip_redundant_nodes(program: &Program) -> Program {
    let transform = SkipRedundantNodesTransform::new(program);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

#[derive(Default, Clone)]
struct SelectionMap(HashMap<NodeIdentifier, Option<SelectionMap>, FnvBuildHasher>);

type Cache = DashMap<PointerAddress, (Transformed<Selection>, SelectionMap)>;

struct SkipRedundantNodesTransform {
    schema: Arc<Schema>,
    cache: Cache,
}

impl<'s> SkipRedundantNodesTransform {
    fn new(program: &'_ Program) -> Self {
        Self {
            schema: Arc::clone(&program.schema),
            cache: DashMap::new(),
        }
    }

    fn transform_selection(
        &self,
        selection: &Selection,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Selection> {
        // This will optimize a traversal of the same subselections.
        // If it's the same node, and selection_map is empty
        // result of transform_selection has to be the same.
        let is_empty = selection_map.0.is_empty();
        if is_empty {
            let key = PointerAddress::new(selection);
            if let Some(cached) = self.cache.get(&key) {
                let (cached_result, cached_selection_map) = cached.clone();
                *selection_map = cached_selection_map;
                return cached_result;
            }
        }
        let identifier = NodeIdentifier::from_selection(&self.schema, selection);
        let result = match selection {
            Selection::ScalarField(_) => {
                if selection_map.0.contains_key(&identifier) {
                    Transformed::Delete
                } else {
                    selection_map.0.insert(identifier, None);
                    Transformed::Keep
                }
            }
            Selection::FragmentSpread(_) => {
                if selection_map.0.contains_key(&identifier) {
                    Transformed::Delete
                } else {
                    selection_map.0.insert(identifier, None);
                    Transformed::Keep
                }
            }
            Selection::LinkedField(selection) => {
                if let Some(Some(linked_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_linked_field(selection, linked_selection_map)
                        .map(Selection::LinkedField)
                } else {
                    let mut linked_selection_map = Default::default();
                    let result = self
                        .transform_linked_field(selection, &mut linked_selection_map)
                        .map(Selection::LinkedField);
                    if !linked_selection_map.0.is_empty() {
                        selection_map
                            .0
                            .insert(identifier, Some(linked_selection_map));
                    }
                    result
                }
            }
            Selection::Condition(selection) => {
                if let Some(Some(existing_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_condition(selection, existing_selection_map)
                        .map(Selection::Condition)
                } else {
                    // Fork the selection map to prevent conditional selections from
                    // affecting the outer "guaranteed" selections.
                    let mut next_selection_map = selection_map.clone();
                    let result = self
                        .transform_condition(selection, &mut next_selection_map)
                        .map(Selection::Condition);
                    selection_map.0.insert(identifier, Some(next_selection_map));
                    result
                }
            }
            Selection::InlineFragment(selection) => {
                if let Some(Some(existing_selection_map)) = selection_map.0.get_mut(&identifier) {
                    self.transform_inline_fragment(selection, existing_selection_map)
                        .map(Selection::InlineFragment)
                } else if selection
                    .directives
                    .iter()
                    .any(is_relay_custom_inline_fragment_directive)
                {
                    let mut linked_selection_map = Default::default();
                    let result = self
                        .transform_inline_fragment(selection, &mut linked_selection_map)
                        .map(Selection::InlineFragment);
                    if !linked_selection_map.0.is_empty() {
                        selection_map
                            .0
                            .insert(identifier, Some(linked_selection_map));
                    }
                    result
                } else {
                    // Fork for inline fragments for the same reason
                    let mut next_selection_map = selection_map.clone();
                    let result = self
                        .transform_inline_fragment(selection, &mut next_selection_map)
                        .map(Selection::InlineFragment);
                    selection_map.0.insert(identifier, Some(next_selection_map));
                    result
                }
            }
        };
        if is_empty {
            let key = PointerAddress::new(selection);
            self.cache
                .insert(key, (result.clone(), selection_map.clone()));
        }
        result
    }

    fn transform_linked_field(
        &self,
        field: &LinkedField,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<LinkedField>> {
        let selections = self.transform_selections(&field.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(LinkedField {
                        selections,
                        ..field.clone()
                    }))
                }
            }
        }
    }

    fn transform_condition(
        &self,
        condition: &Condition,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<Condition>> {
        let selections = self.transform_selections(&condition.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(Condition {
                        selections,
                        ..condition.clone()
                    }))
                }
            }
        }
    }

    fn transform_inline_fragment(
        &self,
        fragment: &InlineFragment,
        selection_map: &mut SelectionMap,
    ) -> Transformed<Arc<InlineFragment>> {
        let selections = self.transform_selections(&fragment.selections, selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                if selections.is_empty() {
                    Transformed::Delete
                } else {
                    Transformed::Replace(Arc::new(InlineFragment {
                        selections,
                        ..fragment.clone()
                    }))
                }
            }
        }
    }

    // Mostly a copy from Transformer::transform_list, but does partition and pass down `selection_map`.
    fn transform_selections(
        &self,
        selections: &[Selection],
        selection_map: &mut SelectionMap,
    ) -> TransformedValue<Vec<Selection>> {
        if selections.is_empty() {
            return TransformedValue::Keep;
        }
        let mut result: Vec<Selection> = Vec::new();
        let mut has_changes = false;
        let selections = get_partitioned_selections(selections);

        for (index, prev_item) in selections.iter().enumerate() {
            let next_item = self.transform_selection(prev_item, selection_map);
            match next_item {
                Transformed::Keep => {
                    if has_changes {
                        result.push((*prev_item).clone());
                    }
                }
                Transformed::Delete => {
                    if !has_changes {
                        debug_assert!(result.capacity() == 0);
                        // assume most items won't be skipped and allocate space for all items
                        result.reserve(selections.len());
                        result.extend(selections.iter().take(index).map(|&x| x.clone()));
                        has_changes = true;
                    }
                }
                Transformed::Replace(next_item) => {
                    if !has_changes {
                        debug_assert!(result.capacity() == 0);
                        // assume most items won't be skipped and allocate space for all items
                        result.reserve(selections.len());
                        result.extend(selections.iter().take(index).map(|&x| x.clone()));
                        has_changes = true;
                    }
                    result.push(next_item);
                }
            }
        }
        if has_changes {
            TransformedValue::Replace(result)
        } else {
            TransformedValue::Replace(selections.iter().map(|&x| x.clone()).collect())
        }
    }

    fn transform_operation(
        &self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        let mut selection_map = Default::default();
        let selections = self.transform_selections(&operation.selections, &mut selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => Transformed::Replace(OperationDefinition {
                selections,
                ..operation.clone()
            }),
        }
    }

    fn transform_fragment(&self, fragment: &FragmentDefinition) -> Transformed<FragmentDefinition> {
        let mut selection_map = Default::default();
        let selections = self.transform_selections(&fragment.selections, &mut selection_map);
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => Transformed::Replace(FragmentDefinition {
                selections,
                ..fragment.clone()
            }),
        }
    }

    fn transform_program(&self, program: &Program) -> TransformedValue<Program> {
        let operations: Vec<Arc<OperationDefinition>> = program
            .par_operations()
            .filter_map(|operation| match self.transform_operation(operation) {
                Transformed::Delete => None,
                Transformed::Keep => Some(Arc::clone(operation)),
                Transformed::Replace(replacement) => Some(Arc::new(replacement)),
            })
            .collect();
        let fragments: Vec<Arc<FragmentDefinition>> = program
            .par_fragments()
            .filter_map(|fragment| match self.transform_fragment(fragment) {
                Transformed::Delete => None,
                Transformed::Keep => Some(Arc::clone(fragment)),
                Transformed::Replace(replacement) => Some(Arc::new(replacement)),
            })
            .collect();
        let mut next_program = Program::new(Arc::clone(&program.schema));
        for operation in operations {
            next_program.insert_operation(operation);
        }
        for fragment in fragments {
            next_program.insert_fragment(fragment);
        }
        TransformedValue::Replace(next_program)
    }
}

/* Selections are sorted with fields first, "conditionals"
 * (inline fragments & conditions) last. This means that all fields that are
 * guaranteed to be fetched are encountered prior to any duplicates that may be
 * fetched within a conditional.
 */
fn get_partitioned_selections(selections: &[Selection]) -> Vec<&Selection> {
    let mut result = Vec::with_capacity(selections.len());
    unsafe { result.set_len(selections.len()) };
    let mut non_field_index = selections
        .iter()
        .filter(|sel| match sel {
            Selection::LinkedField(_) | Selection::ScalarField(_) => true,
            _ => false,
        })
        .count();
    let mut field_index = 0;
    for sel in selections.iter() {
        match sel {
            Selection::LinkedField(_) | Selection::ScalarField(_) => {
                result[field_index] = sel;
                field_index += 1;
            }
            _ => {
                result[non_field_index] = sel;
                non_field_index += 1;
            }
        }
    }
    result
}
<|endoftext|>


<commit_before>use clippy_utils::diagnostics::span_lint_and_sugg;
// use clippy_utils::is_integer_const;
use clippy_utils::consts::{miri_to_const, Constant};
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind, TyKind, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// ### What it does
    /// Displays a warning when a struct with a trailing zero-sized array is declared without the `repr(C)` attribute.
    ///
    /// ### Why is this bad?
    /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code (or in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` is needed.
    ///
    /// ### Example
    /// ```rust
    /// struct RarelyUseful {
    ///     some_field: usize,
    ///     last: [SomeType; 0],
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// #[repr(C)]
    /// struct MakesSense {
    ///     some_field: usize,
    ///     last: [SomeType; 0],
    /// }
    /// ```
    pub TRAILING_ZERO_SIZED_ARRAY_WITHOUT_REPR_C,
    nursery,
    "struct with a trailing zero-sized array but without `repr(C)`"
}
declare_lint_pass!(TrailingZeroSizedArrayWithoutReprC => [TRAILING_ZERO_SIZED_ARRAY_WITHOUT_REPR_C]);

//
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_early_pass(||
// Box::new(trailing_zero_sized_array_without_repr_c::TrailingZeroSizedArrayWithoutReprC));
// DONE!

impl<'tcx> LateLintPass<'tcx> for TrailingZeroSizedArrayWithoutReprC {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
        if is_struct_with_trailing_zero_sized_array(cx, item)
        /* && !has_repr_c(cx, item) */
        {
            // span_lint_and_sugg(
            //     cx,
            //     todo!(),
            //     todo!(),
            //     todo!(),
            //     "try",
            //     "`#[repr(C)]`".to_string(),
            //     Applicability::MachineApplicable,
            // );
            // println!("consider yourself linted 😎");
        }
    }
}

fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
    dbg!(item.ident);
    if_chain! {
        if let ItemKind::Struct(data, _generics) = &item.kind;
        if let VariantData::Struct(field_defs, _) = data;
        if let Some(last_field) = field_defs.last();
        if let TyKind::Array(_, aconst) = last_field.ty.kind;
        let aconst_def_id = cx.tcx.hir().body_owner_def_id(aconst.body).to_def_id();
        let ty = cx.tcx.type_of(aconst_def_id);
        let constant = cx
            .tcx
            .const_eval_poly(aconst_def_id) // NOTE: maybe const_eval_resolve? seems especially cursed to be using a const expr which resolves to 0 to create a zero-sized array, tho
            .ok()
            .map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty));
        if let Some(Constant::Int(val)) = constant.and_then(miri_to_const);
        if val == 0;
        then {
            eprintln!("true");
            true
        } else {
            // dbg!(aconst);
            eprintln!("false");
            false
        }
    }
}

fn has_repr_c(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
    // todo!()
    true
}
<commit_msg>Implement getting an array of attributes!<commit_after>use clippy_utils::diagnostics::span_lint_and_sugg;
// use clippy_utils::is_integer_const;
use clippy_utils::consts::{miri_to_const, Constant};
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind, TyKind, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;

declare_clippy_lint! {
    /// ### What it does
    /// Displays a warning when a struct with a trailing zero-sized array is declared without the `repr(C)` attribute.
    ///
    /// ### Why is this bad?
    /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code (or in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` is needed.
    ///
    /// ### Example
    /// ```rust
    /// struct RarelyUseful {
    ///     some_field: usize,
    ///     last: [SomeType; 0],
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust
    /// #[repr(C)]
    /// struct MakesSense {
    ///     some_field: usize,
    ///     last: [SomeType; 0],
    /// }
    /// ```
    pub TRAILING_ZERO_SIZED_ARRAY_WITHOUT_REPR_C,
    nursery,
    "struct with a trailing zero-sized array but without `repr(C)`"
}
declare_lint_pass!(TrailingZeroSizedArrayWithoutReprC => [TRAILING_ZERO_SIZED_ARRAY_WITHOUT_REPR_C]);

//
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
//       e.g. store.register_early_pass(||
// Box::new(trailing_zero_sized_array_without_repr_c::TrailingZeroSizedArrayWithoutReprC));
// DONE!

impl<'tcx> LateLintPass<'tcx> for TrailingZeroSizedArrayWithoutReprC {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
        dbg!(item.ident);
        if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_c(cx, item) {
            // span_lint_and_sugg(
            //     cx,
            //     todo!(),
            //     todo!(),
            //     todo!(),
            //     "try",
            //     "`#[repr(C)]`".to_string(),
            //     Applicability::MachineApplicable,
            // );
            // println!("consider yourself linted 😎");
        }
    }
}

fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
    if let ItemKind::Struct(data, _generics) = &item.kind {
        if let VariantData::Struct(field_defs, _) = data {
            if let Some(last_field) = field_defs.last() {
                if let TyKind::Array(_, aconst) = last_field.ty.kind {
                    let aconst_def_id = cx.tcx.hir().body_owner_def_id(aconst.body).to_def_id();
                    let ty = cx.tcx.type_of(aconst_def_id);
                    let constant = cx
                        .tcx
                        // NOTE: maybe const_eval_resolve? seems especially cursed to be using a const expr which
                        // resolves to 0 to create a zero-sized array, tho
                        .const_eval_poly(aconst_def_id)
                        .ok()
                        .map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty));
                    if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
                        if val == 0 {
                            eprintln!("trailing: true");
                            return true;
                        }
                    }
                }
            }
        }
    }
    // dbg!(aconst);
    eprintln!("trailing: false");
    false
}

fn has_repr_c(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
    // let hir_id2 = if let Some(body) = cx.enclosing_body {
    //     body.hir_id
    // } else {
    //     todo!();
    // };

    let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id);
    let attrs = cx.tcx.hir().attrs(hir_id);
    // NOTE: Can there ever be more than one `repr` attribute?
    // other `repr` syms: repr, repr128, repr_align, repr_align_enum, repr_no_niche, repr_packed,
    // repr_simd, repr_transparent

    if let Some(repr_attr) = attrs.iter().find(|attr| attr.has_name(sym::repr)) {
        eprintln!("repr: true");
        true
    } else {
        eprintln!("repr: false");
        false
    }
}
<|endoftext|>


<commit_before>use crate::bounding_volume::AABB;
use crate::math::{Isometry, Point, Vector, DIM};
use na::{self, Real};
use crate::num::{Bounded, Zero};
use crate::query::{PointProjection, PointQuery};
use crate::shape::FeatureId;
use crate::utils::IsometryOps;

impl<N: Real> AABB<N> {
    fn local_point_projection(
        &self,
        m: &Isometry<N>,
        pt: &Point<N>,
        solid: bool,
    ) -> (bool, Point<N>, Vector<N>)
    {
        let ls_pt = m.inverse_transform_point(pt);
        let mins_pt = *self.mins() - ls_pt;
        let pt_maxs = ls_pt - *self.maxs();
        let shift = na::sup(&na::zero(), &mins_pt) - na::sup(&na::zero(), &pt_maxs);

        let inside = shift == na::zero();

        if !inside {
            (false, ls_pt + shift, shift)
        } else if solid {
            (true, ls_pt, shift)
        } else {
            let _max: N = Bounded::max_value();
            let mut best = -_max;
            let mut best_id = 0isize;

            for i in 0..DIM {
                let mins_pt_i = mins_pt[i];
                let pt_maxs_i = pt_maxs[i];

                if mins_pt_i < pt_maxs_i {
                    if pt_maxs[i] > best {
                        best_id = i as isize;
                        best = pt_maxs_i
                    }
                } else if mins_pt_i > best {
                    best_id = -(i as isize);
                    best = mins_pt_i
                }
            }

            let mut shift: Vector<N> = na::zero();

            if best_id < 0 {
                shift[(-best_id) as usize] = best;
            } else {
                shift[best_id as usize] = -best;
            }

            (inside, ls_pt + shift, shift)
        }
    }
}

impl<N: Real> PointQuery<N> for AABB<N> {
    #[inline]
    fn project_point(&self, m: &Isometry<N>, pt: &Point<N>, solid: bool) -> PointProjection<N> {
        let (inside, ls_pt, _) = self.local_point_projection(m, pt, solid);
        PointProjection::new(inside, m * ls_pt)
    }

    #[allow(unused_assignments)] // For last_zero_shift which is used only in 3D.
    #[allow(unused_variables)] // For last_zero_shift which is used only in 3D.
    #[inline]
    fn project_point_with_feature(
        &self,
        m: &Isometry<N>,
        pt: &Point<N>,
    ) -> (PointProjection<N>, FeatureId)
    {
        let (inside, ls_pt, shift) = self.local_point_projection(m, pt, false);
        let proj = PointProjection::new(inside, m * ls_pt);
        let mut nzero_shifts = 0;
        let mut last_zero_shift = 0;
        let mut last_not_zero_shift = 0;

        for i in 0..DIM {
            if shift[i].is_zero() {
                nzero_shifts += 1;
                last_zero_shift = i;
            } else {
                last_not_zero_shift = i;
            }
        }

        if nzero_shifts == DIM {
            for i in 0..DIM {
                if ls_pt[i] > self.maxs()[i] - N::default_epsilon() {
                    return (proj, FeatureId::Face(i));
                }
                if ls_pt[i] <= self.mins()[i] + N::default_epsilon() {
                    return (proj, FeatureId::Face(i + DIM));
                }
            }

            unreachable!()
        } else if nzero_shifts == DIM - 1 {
            // On a 3D face.
            if ls_pt[last_not_zero_shift] < self.center()[last_not_zero_shift] {
                (proj, FeatureId::Face(last_not_zero_shift + DIM))
            } else {
                (proj, FeatureId::Face(last_not_zero_shift))
            }
        } else {
            // On a vertex or edge.
            let mut id = 0;
            let center = self.center();

            for i in 0..DIM {
                if ls_pt[i] < center[i] {
                    id |= 1 << i;
                }
            }

            #[cfg(feature = "dim3")]
            {
                if nzero_shifts == 0 {
                    (proj, FeatureId::Vertex(id))
                } else {
                    (proj, FeatureId::Edge((id << 2) | last_zero_shift))
                }
            }

            #[cfg(feature = "dim2")]
            {
                (proj, FeatureId::Vertex(id))
            }
        }
    }

    #[inline]
    fn distance_to_point(&self, m: &Isometry<N>, pt: &Point<N>, solid: bool) -> N {
        let ls_pt = m.inverse_transform_point(pt);
        let mins_pt = *self.mins() - ls_pt;
        let pt_maxs = ls_pt - *self.maxs();
        let shift = na::sup(&na::zero(), &na::sup(&mins_pt, &pt_maxs));

        if solid || !shift.is_zero() {
            shift.norm()
        } else {
            // FIXME: optimize that.
            -na::distance(pt, &self.project_point(m, pt, solid).point)
        }
    }

    #[inline]
    fn contains_point(&self, m: &Isometry<N>, pt: &Point<N>) -> bool {
        let ls_pt = m.inverse_transform_point(pt).coords;

        for i in 0..DIM {
            if ls_pt[i] < self.mins()[i] || ls_pt[i] > self.maxs()[i] {
                return false;
            }
        }

        true
    }
}
<commit_msg>Fix unreachable!() in point-aabb projection.<commit_after>use crate::bounding_volume::AABB;
use crate::math::{Isometry, Point, Vector, DIM};
use na::{self, Real};
use crate::num::{Bounded, Zero};
use crate::query::{PointProjection, PointQuery};
use crate::shape::FeatureId;
use crate::utils::IsometryOps;

impl<N: Real> AABB<N> {
    fn local_point_projection(
        &self,
        m: &Isometry<N>,
        pt: &Point<N>,
        solid: bool,
    ) -> (bool, Point<N>, Vector<N>)
    {
        let ls_pt = m.inverse_transform_point(pt);
        let mins_pt = *self.mins() - ls_pt;
        let pt_maxs = ls_pt - *self.maxs();
        let shift = na::sup(&na::zero(), &mins_pt) - na::sup(&na::zero(), &pt_maxs);

        let inside = shift == na::zero();

        if !inside {
            (false, ls_pt + shift, shift)
        } else if solid {
            (true, ls_pt, shift)
        } else {
            let _max: N = Bounded::max_value();
            let mut best = -_max;
            let mut best_id = 0isize;

            for i in 0..DIM {
                let mins_pt_i = mins_pt[i];
                let pt_maxs_i = pt_maxs[i];

                if mins_pt_i < pt_maxs_i {
                    if pt_maxs[i] > best {
                        best_id = i as isize;
                        best = pt_maxs_i
                    }
                } else if mins_pt_i > best {
                    best_id = -(i as isize);
                    best = mins_pt_i
                }
            }

            let mut shift: Vector<N> = na::zero();

            if best_id < 0 {
                shift[(-best_id) as usize] = best;
            } else {
                shift[best_id as usize] = -best;
            }

            (inside, ls_pt + shift, shift)
        }
    }
}

impl<N: Real> PointQuery<N> for AABB<N> {
    #[inline]
    fn project_point(&self, m: &Isometry<N>, pt: &Point<N>, solid: bool) -> PointProjection<N> {
        let (inside, ls_pt, _) = self.local_point_projection(m, pt, solid);
        PointProjection::new(inside, m * ls_pt)
    }

    #[allow(unused_assignments)] // For last_zero_shift which is used only in 3D.
    #[allow(unused_variables)] // For last_zero_shift which is used only in 3D.
    #[inline]
    fn project_point_with_feature(
        &self,
        m: &Isometry<N>,
        pt: &Point<N>,
    ) -> (PointProjection<N>, FeatureId)
    {
        let (inside, ls_pt, shift) = self.local_point_projection(m, pt, false);
        let proj = PointProjection::new(inside, m * ls_pt);
        let mut nzero_shifts = 0;
        let mut last_zero_shift = 0;
        let mut last_not_zero_shift = 0;

        for i in 0..DIM {
            if shift[i].is_zero() {
                nzero_shifts += 1;
                last_zero_shift = i;
            } else {
                last_not_zero_shift = i;
            }
        }

        if nzero_shifts == DIM {
            for i in 0..DIM {
                if ls_pt[i] > self.maxs()[i] - N::default_epsilon() {
                    return (proj, FeatureId::Face(i));
                }
                if ls_pt[i] <= self.mins()[i] + N::default_epsilon() {
                    return (proj, FeatureId::Face(i + DIM));
                }
            }

            (proj, FeatureId::Unknown)
        } else if nzero_shifts == DIM - 1 {
            // On a 3D face.
            if ls_pt[last_not_zero_shift] < self.center()[last_not_zero_shift] {
                (proj, FeatureId::Face(last_not_zero_shift + DIM))
            } else {
                (proj, FeatureId::Face(last_not_zero_shift))
            }
        } else {
            // On a vertex or edge.
            let mut id = 0;
            let center = self.center();

            for i in 0..DIM {
                if ls_pt[i] < center[i] {
                    id |= 1 << i;
                }
            }

            #[cfg(feature = "dim3")]
            {
                if nzero_shifts == 0 {
                    (proj, FeatureId::Vertex(id))
                } else {
                    (proj, FeatureId::Edge((id << 2) | last_zero_shift))
                }
            }

            #[cfg(feature = "dim2")]
            {
                (proj, FeatureId::Vertex(id))
            }
        }
    }

    #[inline]
    fn distance_to_point(&self, m: &Isometry<N>, pt: &Point<N>, solid: bool) -> N {
        let ls_pt = m.inverse_transform_point(pt);
        let mins_pt = *self.mins() - ls_pt;
        let pt_maxs = ls_pt - *self.maxs();
        let shift = na::sup(&na::zero(), &na::sup(&mins_pt, &pt_maxs));

        if solid || !shift.is_zero() {
            shift.norm()
        } else {
            // FIXME: optimize that.
            -na::distance(pt, &self.project_point(m, pt, solid).point)
        }
    }

    #[inline]
    fn contains_point(&self, m: &Isometry<N>, pt: &Point<N>) -> bool {
        let ls_pt = m.inverse_transform_point(pt).coords;

        for i in 0..DIM {
            if ls_pt[i] < self.mins()[i] || ls_pt[i] > self.maxs()[i] {
                return false;
            }
        }

        true
    }
}
<|endoftext|>


<commit_before>use crate::{
    proc_macros::IntoRenderObject,
    render_object::*,
    utils::{Brush, Point, Rectangle},
};

/// Used to render a text.
#[derive(Debug, IntoRenderObject)]
pub struct TextRenderObject;

impl RenderObject for TextRenderObject {
    fn render_self(&self, ctx: &mut Context, global_position: &Point) {
        let (bounds, text, foreground, font, font_size, offset) = {
            let widget = ctx.widget();
            let text = widget.clone::<String>("text");
            let offset = *widget.get::<f64>("offset");

            let txt = {
                if !text.is_empty() {
                    text
                } else {
                    widget.clone_or_default::<String>("water_mark")
                }
            };
            (
                *widget.get::<Rectangle>("bounds"),
                txt.to_string(),
                widget.get::<Brush>("foreground").clone(),
                widget.get::<String>("font").clone(),
                *widget.get::<f64>("font_size"),
                offset,
            )
        };

        if bounds.width() == 0.0
            || bounds.height() == 0.0
            || foreground.is_transparent()
            || font_size == 0.0
            || text.is_empty()
        {
            return;
        }

        if !text.is_empty() {
            ctx.render_context_2_d().begin_path();
            ctx.render_context_2_d().set_font_family(font);
            ctx.render_context_2_d().set_font_size(font_size);
            ctx.render_context_2_d().set_fill_style(foreground);

            ctx.render_context_2_d().fill_text(
                &text,
                global_position.x() + bounds.x() + offset,
                global_position.y() + bounds.y(),
            );
            ctx.render_context_2_d().close_path();
        }
    }
}
<commit_msg>lint fix<commit_after>use crate::{
    proc_macros::IntoRenderObject,
    render_object::*,
    utils::{Brush, Point, Rectangle},
};

/// Used to render a text.
#[derive(Debug, IntoRenderObject)]
pub struct TextRenderObject;

impl RenderObject for TextRenderObject {
    fn render_self(&self, ctx: &mut Context, global_position: &Point) {
        let (bounds, text, foreground, font, font_size, offset) = {
            let widget = ctx.widget();
            let text = widget.clone::<String>("text");
            let offset = *widget.get::<f64>("offset");

            let txt = {
                if !text.is_empty() {
                    text
                } else {
                    widget.clone_or_default::<String>("water_mark")
                }
            };
            (
                *widget.get::<Rectangle>("bounds"),
                txt,
                widget.get::<Brush>("foreground").clone(),
                widget.get::<String>("font").clone(),
                *widget.get::<f64>("font_size"),
                offset,
            )
        };

        if bounds.width() == 0.0
            || bounds.height() == 0.0
            || foreground.is_transparent()
            || font_size == 0.0
            || text.is_empty()
        {
            return;
        }

        if !text.is_empty() {
            ctx.render_context_2_d().begin_path();
            ctx.render_context_2_d().set_font_family(font);
            ctx.render_context_2_d().set_font_size(font_size);
            ctx.render_context_2_d().set_fill_style(foreground);

            ctx.render_context_2_d().fill_text(
                &text,
                global_position.x() + bounds.x() + offset,
                global_position.y() + bounds.y(),
            );
            ctx.render_context_2_d().close_path();
        }
    }
}
<|endoftext|>


<commit_before>use std::marker::PhantomData;

use ra_syntax::{
    File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef,
    ast::{self, AstNode},
};

use crate::FileId;
use crate::db::SyntaxDatabase;

salsa::query_group! {
    pub(crate) trait SyntaxPtrDatabase: SyntaxDatabase {
        fn resolve_syntax_ptr(ptr: SyntaxPtr) -> SyntaxNode {
            type ResolveSyntaxPtrQuery;
            storage volatile;
        }
    }
}

fn resolve_syntax_ptr(db: &impl SyntaxDatabase, ptr: SyntaxPtr) -> SyntaxNode {
    let syntax = db.file_syntax(ptr.file_id);
    ptr.local.resolve(&syntax)
}

/// SyntaxPtr is a cheap `Copy` id which identifies a particular syntax node,
/// without retainig syntax tree in memory. You need to explicitelly `resovle`
/// `SyntaxPtr` to get a `SyntaxNode`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct SyntaxPtr {
    file_id: FileId,
    local: LocalSyntaxPtr,
}

impl SyntaxPtr {
    pub(crate) fn new(file_id: FileId, node: SyntaxNodeRef) -> SyntaxPtr {
        let local = LocalSyntaxPtr::new(node);
        SyntaxPtr { file_id, local }
    }
}

struct OwnedAst<T> {
    syntax: SyntaxNode,
    phantom: PhantomData<T>,
}

trait ToAst {
    type Ast;
    fn to_ast(self) -> Self::Ast;
}

impl<'a> ToAst for &'a OwnedAst<ast::FnDef<'static>> {
    type Ast = ast::FnDef<'a>;
    fn to_ast(self) -> ast::FnDef<'a> {
        ast::FnDef::cast(self.syntax.borrowed())
            .unwrap()
    }
}

/// A pionter to a syntax node inside a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct LocalSyntaxPtr {
    range: TextRange,
    kind: SyntaxKind,
}

impl LocalSyntaxPtr {
    pub(crate) fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr {
        LocalSyntaxPtr {
            range: node.range(),
            kind: node.kind(),
        }
    }

    pub(crate) fn resolve(self, file: &File) -> SyntaxNode {
        let mut curr = file.syntax();
        loop {
            if curr.range() == self.range && curr.kind() == self.kind {
                return curr.owned();
            }
            curr = curr.children()
                .find(|it| self.range.is_subrange(&it.range()))
                .unwrap_or_else(|| panic!("can't resovle local ptr to SyntaxNode: {:?}", self))
        }
    }
}

#[test]
fn test_local_syntax_ptr() {
    let file = File::parse("struct Foo { f: u32, }");
    let field = file.syntax().descendants().find_map(ast::NamedFieldDef::cast).unwrap();
    let ptr = LocalSyntaxPtr::new(field.syntax());
    let field_syntax = ptr.resolve(&file);
    assert_eq!(field.syntax(), field_syntax);
}
<commit_msg>Fix typos<commit_after>use std::marker::PhantomData;

use ra_syntax::{
    File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef,
    ast::{self, AstNode},
};

use crate::FileId;
use crate::db::SyntaxDatabase;

salsa::query_group! {
    pub(crate) trait SyntaxPtrDatabase: SyntaxDatabase {
        fn resolve_syntax_ptr(ptr: SyntaxPtr) -> SyntaxNode {
            type ResolveSyntaxPtrQuery;
            storage volatile;
        }
    }
}

fn resolve_syntax_ptr(db: &impl SyntaxDatabase, ptr: SyntaxPtr) -> SyntaxNode {
    let syntax = db.file_syntax(ptr.file_id);
    ptr.local.resolve(&syntax)
}

/// SyntaxPtr is a cheap `Copy` id which identifies a particular syntax node,
/// without retaining syntax tree in memory. You need to explicitly `resolve`
/// `SyntaxPtr` to get a `SyntaxNode`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct SyntaxPtr {
    file_id: FileId,
    local: LocalSyntaxPtr,
}

impl SyntaxPtr {
    pub(crate) fn new(file_id: FileId, node: SyntaxNodeRef) -> SyntaxPtr {
        let local = LocalSyntaxPtr::new(node);
        SyntaxPtr { file_id, local }
    }
}

struct OwnedAst<T> {
    syntax: SyntaxNode,
    phantom: PhantomData<T>,
}

trait ToAst {
    type Ast;
    fn to_ast(self) -> Self::Ast;
}

impl<'a> ToAst for &'a OwnedAst<ast::FnDef<'static>> {
    type Ast = ast::FnDef<'a>;
    fn to_ast(self) -> ast::FnDef<'a> {
        ast::FnDef::cast(self.syntax.borrowed())
            .unwrap()
    }
}

/// A pionter to a syntax node inside a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct LocalSyntaxPtr {
    range: TextRange,
    kind: SyntaxKind,
}

impl LocalSyntaxPtr {
    pub(crate) fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr {
        LocalSyntaxPtr {
            range: node.range(),
            kind: node.kind(),
        }
    }

    pub(crate) fn resolve(self, file: &File) -> SyntaxNode {
        let mut curr = file.syntax();
        loop {
            if curr.range() == self.range && curr.kind() == self.kind {
                return curr.owned();
            }
            curr = curr.children()
                .find(|it| self.range.is_subrange(&it.range()))
                .unwrap_or_else(|| panic!("can't resolve local ptr to SyntaxNode: {:?}", self))
        }
    }
}

#[test]
fn test_local_syntax_ptr() {
    let file = File::parse("struct Foo { f: u32, }");
    let field = file.syntax().descendants().find_map(ast::NamedFieldDef::cast).unwrap();
    let ptr = LocalSyntaxPtr::new(field.syntax());
    let field_syntax = ptr.resolve(&file);
    assert_eq!(field.syntax(), field_syntax);
}
<|endoftext|>