<commit_before>extern crate tempdir;

use boxfuture::{BoxFuture, Boxable};
use fs::{self, PathStatGetter};
use futures::{future, Future};
use std::process::Command;
use std::sync::Arc;

use super::{ExecuteProcessRequest, ExecuteProcessResult};

use bytes::Bytes;

pub struct CommandRunner {
  store: fs::Store,
  fs_pool: Arc<fs::ResettablePool>,
}

impl CommandRunner {
  pub fn new(store: fs::Store, fs_pool: Arc<fs::ResettablePool>) -> CommandRunner {
    CommandRunner { store, fs_pool }
  }
}

impl super::CommandRunner for CommandRunner {
  ///
  /// Runs a command on this machine in the passed working directory.
  ///
  /// This takes ownership of a TempDir rather than a Path to ensure that the TempDir can't be
  /// dropped, and thus the underlying directory deleted, while we need a reference to it. If we
  /// switch to not use TempDir, we should ensure that this guarantee still holds.
  ///
  fn run(&self, req: ExecuteProcessRequest) -> BoxFuture<ExecuteProcessResult, String> {
    let workdir = try_future!(
      tempdir::TempDir::new("process-execution").map_err(|err| format!(
        "Error making tempdir for local process execution: {:?}",
        err
      ))
    );

    let store = self.store.clone();
    let fs_pool = self.fs_pool.clone();
    let env = req.env;
    let output_file_paths = req.output_files;
    let argv = req.argv;
    self
      .store
      .materialize_directory(workdir.path().to_owned(), req.input_files)
      .and_then(move |()| {
        Command::new(&argv[0])
                  .args(&argv[1..])
                  .current_dir(workdir.path())
                  .env_clear()
                  // It would be really nice not to have to manually set PATH but this is sadly the only way
                  // to stop automatic PATH searching.
                  .env("PATH", "")
                  .envs(env)
                  .output().map_err(|e| format!("Error executing process: {:?}", e))
                  .map(|output| (output, workdir))
      })
      .and_then(|(output, workdir)| {
        let output_snapshot = if output_file_paths.is_empty() {
          future::ok(fs::Snapshot::empty()).to_boxed()
        } else {
          // Use no ignore patterns, because we are looking for explicitly listed paths.
          future::done(fs::PosixFS::new(
                  workdir.path(),
                  fs_pool,
                  vec![],
              )).map_err(|err| {
                  format!(
                      "Error making posix_fs to fetch local process execution output files: {}",
                      err
                  )
              })
                  .map(|posix_fs| Arc::new(posix_fs))
                  .and_then(|posix_fs| {
                      posix_fs
                          .path_stats(output_file_paths.into_iter().collect())
                          .map_err(|e| format!("Error stating output files: {}", e))
                          .and_then(move |paths| {
                              fs::Snapshot::from_path_stats(
                                  store.clone(),
                                  fs::OneOffStoreFileByDigest::new(store, posix_fs),
                                  paths.into_iter().filter_map(|v| v).collect(),
                              )
                          })
                  })
                  // Force workdir not to get dropped until after we've ingested the outputs
                  .map(|result| (result, workdir))
                  .map(|(result, _workdir)| result)
                  .to_boxed()
        };

        output_snapshot
          .map(|snapshot| ExecuteProcessResult {
            stdout: Bytes::from(output.stdout),
            stderr: Bytes::from(output.stderr),
            exit_code: output.status.code().unwrap(),
            output_directory: snapshot.digest,
          })
          .to_boxed()
      })
      .to_boxed()
  }
}

#[cfg(test)]
mod tests {
  extern crate tempdir;
  extern crate testutil;

  use fs;
  use futures::Future;
  use super::{ExecuteProcessRequest, ExecuteProcessResult};
  use super::super::CommandRunner as CommandRunnerTrait;
  use std;
  use std::collections::{BTreeMap, BTreeSet};
  use std::env;
  use std::os::unix::fs::PermissionsExt;
  use std::path::{Path, PathBuf};
  use std::sync::Arc;
  use tempdir::TempDir;
  use self::testutil::{as_bytes, owned_string_vec};
  use testutil::data::{TestData, TestDirectory};

  #[test]
  #[cfg(unix)]
  fn stdout() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/bin/echo", "-n", "foo"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes("foo"),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  #[cfg(unix)]
  fn stdout_and_stderr_and_exit_code() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/bin/bash", "-c", "echo -n foo ; echo >&2 -n bar ; exit 1"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes("foo"),
        stderr: as_bytes("bar"),
        exit_code: 1,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  #[cfg(unix)]
  fn env() {
    let mut env: BTreeMap<String, String> = BTreeMap::new();
    env.insert("FOO".to_string(), "foo".to_string());
    env.insert("BAR".to_string(), "not foo".to_string());

    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/usr/bin/env"]),
      env: env.clone(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    let stdout = String::from_utf8(result.unwrap().stdout.to_vec()).unwrap();
    let got_env: BTreeMap<String, String> = stdout
      .split("\n")
      .filter(|line| !line.is_empty())
      .map(|line| line.splitn(2, "="))
      .map(|mut parts| {
        (
          parts.next().unwrap().to_string(),
          parts.next().unwrap_or("").to_string(),
        )
      })
      .filter(|x| x.0 != "PATH")
      .collect();

    assert_eq!(env, got_env);
  }

  #[test]
  #[cfg(unix)]
  fn env_is_deterministic() {
    fn make_request() -> ExecuteProcessRequest {
      let mut env = BTreeMap::new();
      env.insert("FOO".to_string(), "foo".to_string());
      env.insert("BAR".to_string(), "not foo".to_string());

      ExecuteProcessRequest {
        argv: owned_string_vec(&["/usr/bin/env"]),
        env: env,
        input_files: fs::EMPTY_DIGEST,
        output_files: BTreeSet::new(),
      }
    }

    let result1 = run_command_locally(make_request());
    let result2 = run_command_locally(make_request());

    assert_eq!(result1.unwrap(), result2.unwrap());
  }

  #[test]
  fn binary_not_found() {
    run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["echo", "-n", "foo"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    }).expect_err("Want Err");
  }

  #[test]
  fn output_files_none() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&[
        which("bash").expect("No bash on PATH").to_str().unwrap(),
        "-c",
        "exit 0",
      ]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });
    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  fn output_files_one() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!("echo -n {} > {}", TestData::roland().string(), "roland"),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland")].into_iter().collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  #[test]
  fn output_files_many() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!(
          "/bin/mkdir cats ; echo -n {} > cats/roland ; echo -n {} > treats",
          TestData::roland().string(),
          TestData::catnip().string()
        ),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("cats/roland"), PathBuf::from("treats")]
        .into_iter()
        .collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::recursive().digest(),
      }
    )
  }

  #[test]
  fn output_files_execution_failure() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!(
          "echo -n {} > {} ; exit 1",
          TestData::roland().string(),
          "roland"
        ),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland")].into_iter().collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 1,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  #[test]
  fn output_files_partial_output() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!("echo -n {} > {}", TestData::roland().string(), "roland"),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland"), PathBuf::from("susannah")]
        .into_iter()
        .collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  fn run_command_locally(req: ExecuteProcessRequest) -> Result<ExecuteProcessResult, String> {
    run_command_locally_in_dir(req)
  }

  fn run_command_locally_in_dir(
    req: ExecuteProcessRequest,
  ) -> Result<ExecuteProcessResult, String> {
    let store_dir = TempDir::new("store").unwrap();
    let pool = Arc::new(fs::ResettablePool::new("test-pool-".to_owned()));
    let store = fs::Store::local_only(store_dir.path(), pool.clone()).unwrap();
    let runner = super::CommandRunner {
      store: store,
      fs_pool: pool,
    };
    runner.run(req).wait()
  }

  fn find_bash() -> String {
    which("bash")
      .expect("No bash on PATH")
      .to_str()
      .expect("Path to bash not unicode")
      .to_owned()
  }

  fn which(executable: &str) -> Option<PathBuf> {
    if let Some(paths) = env::var_os("PATH") {
      for path in env::split_paths(&paths) {
        let executable_path = path.join(executable);
        if is_executable(&executable_path) {
          return Some(executable_path);
        }
      }
    }
    None
  }

  fn is_executable(path: &Path) -> bool {
    std::fs::metadata(path)
      .map(|meta| meta.permissions().mode() & 0o100 == 0o100)
      .unwrap_or(false)
  }
}
<commit_msg>Remove stale comment (#5839)<commit_after>extern crate tempdir;

use boxfuture::{BoxFuture, Boxable};
use fs::{self, PathStatGetter};
use futures::{future, Future};
use std::process::Command;
use std::sync::Arc;

use super::{ExecuteProcessRequest, ExecuteProcessResult};

use bytes::Bytes;

pub struct CommandRunner {
  store: fs::Store,
  fs_pool: Arc<fs::ResettablePool>,
}

impl CommandRunner {
  pub fn new(store: fs::Store, fs_pool: Arc<fs::ResettablePool>) -> CommandRunner {
    CommandRunner { store, fs_pool }
  }
}

impl super::CommandRunner for CommandRunner {
  ///
  /// Runs a command on this machine in the passed working directory.
  ///
  fn run(&self, req: ExecuteProcessRequest) -> BoxFuture<ExecuteProcessResult, String> {
    let workdir = try_future!(
      tempdir::TempDir::new("process-execution").map_err(|err| format!(
        "Error making tempdir for local process execution: {:?}",
        err
      ))
    );

    let store = self.store.clone();
    let fs_pool = self.fs_pool.clone();
    let env = req.env;
    let output_file_paths = req.output_files;
    let argv = req.argv;
    self
      .store
      .materialize_directory(workdir.path().to_owned(), req.input_files)
      .and_then(move |()| {
        Command::new(&argv[0])
                  .args(&argv[1..])
                  .current_dir(workdir.path())
                  .env_clear()
                  // It would be really nice not to have to manually set PATH but this is sadly the only way
                  // to stop automatic PATH searching.
                  .env("PATH", "")
                  .envs(env)
                  .output().map_err(|e| format!("Error executing process: {:?}", e))
                  .map(|output| (output, workdir))
      })
      .and_then(|(output, workdir)| {
        let output_snapshot = if output_file_paths.is_empty() {
          future::ok(fs::Snapshot::empty()).to_boxed()
        } else {
          // Use no ignore patterns, because we are looking for explicitly listed paths.
          future::done(fs::PosixFS::new(
                  workdir.path(),
                  fs_pool,
                  vec![],
              )).map_err(|err| {
                  format!(
                      "Error making posix_fs to fetch local process execution output files: {}",
                      err
                  )
              })
                  .map(|posix_fs| Arc::new(posix_fs))
                  .and_then(|posix_fs| {
                      posix_fs
                          .path_stats(output_file_paths.into_iter().collect())
                          .map_err(|e| format!("Error stating output files: {}", e))
                          .and_then(move |paths| {
                              fs::Snapshot::from_path_stats(
                                  store.clone(),
                                  fs::OneOffStoreFileByDigest::new(store, posix_fs),
                                  paths.into_iter().filter_map(|v| v).collect(),
                              )
                          })
                  })
                  // Force workdir not to get dropped until after we've ingested the outputs
                  .map(|result| (result, workdir))
                  .map(|(result, _workdir)| result)
                  .to_boxed()
        };

        output_snapshot
          .map(|snapshot| ExecuteProcessResult {
            stdout: Bytes::from(output.stdout),
            stderr: Bytes::from(output.stderr),
            exit_code: output.status.code().unwrap(),
            output_directory: snapshot.digest,
          })
          .to_boxed()
      })
      .to_boxed()
  }
}

#[cfg(test)]
mod tests {
  extern crate tempdir;
  extern crate testutil;

  use fs;
  use futures::Future;
  use super::{ExecuteProcessRequest, ExecuteProcessResult};
  use super::super::CommandRunner as CommandRunnerTrait;
  use std;
  use std::collections::{BTreeMap, BTreeSet};
  use std::env;
  use std::os::unix::fs::PermissionsExt;
  use std::path::{Path, PathBuf};
  use std::sync::Arc;
  use tempdir::TempDir;
  use self::testutil::{as_bytes, owned_string_vec};
  use testutil::data::{TestData, TestDirectory};

  #[test]
  #[cfg(unix)]
  fn stdout() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/bin/echo", "-n", "foo"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes("foo"),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  #[cfg(unix)]
  fn stdout_and_stderr_and_exit_code() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/bin/bash", "-c", "echo -n foo ; echo >&2 -n bar ; exit 1"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes("foo"),
        stderr: as_bytes("bar"),
        exit_code: 1,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  #[cfg(unix)]
  fn env() {
    let mut env: BTreeMap<String, String> = BTreeMap::new();
    env.insert("FOO".to_string(), "foo".to_string());
    env.insert("BAR".to_string(), "not foo".to_string());

    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["/usr/bin/env"]),
      env: env.clone(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });

    let stdout = String::from_utf8(result.unwrap().stdout.to_vec()).unwrap();
    let got_env: BTreeMap<String, String> = stdout
      .split("\n")
      .filter(|line| !line.is_empty())
      .map(|line| line.splitn(2, "="))
      .map(|mut parts| {
        (
          parts.next().unwrap().to_string(),
          parts.next().unwrap_or("").to_string(),
        )
      })
      .filter(|x| x.0 != "PATH")
      .collect();

    assert_eq!(env, got_env);
  }

  #[test]
  #[cfg(unix)]
  fn env_is_deterministic() {
    fn make_request() -> ExecuteProcessRequest {
      let mut env = BTreeMap::new();
      env.insert("FOO".to_string(), "foo".to_string());
      env.insert("BAR".to_string(), "not foo".to_string());

      ExecuteProcessRequest {
        argv: owned_string_vec(&["/usr/bin/env"]),
        env: env,
        input_files: fs::EMPTY_DIGEST,
        output_files: BTreeSet::new(),
      }
    }

    let result1 = run_command_locally(make_request());
    let result2 = run_command_locally(make_request());

    assert_eq!(result1.unwrap(), result2.unwrap());
  }

  #[test]
  fn binary_not_found() {
    run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&["echo", "-n", "foo"]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    }).expect_err("Want Err");
  }

  #[test]
  fn output_files_none() {
    let result = run_command_locally(ExecuteProcessRequest {
      argv: owned_string_vec(&[
        which("bash").expect("No bash on PATH").to_str().unwrap(),
        "-c",
        "exit 0",
      ]),
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: BTreeSet::new(),
    });
    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: fs::EMPTY_DIGEST,
      }
    )
  }

  #[test]
  fn output_files_one() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!("echo -n {} > {}", TestData::roland().string(), "roland"),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland")].into_iter().collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  #[test]
  fn output_files_many() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!(
          "/bin/mkdir cats ; echo -n {} > cats/roland ; echo -n {} > treats",
          TestData::roland().string(),
          TestData::catnip().string()
        ),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("cats/roland"), PathBuf::from("treats")]
        .into_iter()
        .collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::recursive().digest(),
      }
    )
  }

  #[test]
  fn output_files_execution_failure() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!(
          "echo -n {} > {} ; exit 1",
          TestData::roland().string(),
          "roland"
        ),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland")].into_iter().collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 1,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  #[test]
  fn output_files_partial_output() {
    let result = run_command_locally_in_dir(ExecuteProcessRequest {
      argv: vec![
        find_bash(),
        "-c".to_owned(),
        format!("echo -n {} > {}", TestData::roland().string(), "roland"),
      ],
      env: BTreeMap::new(),
      input_files: fs::EMPTY_DIGEST,
      output_files: vec![PathBuf::from("roland"), PathBuf::from("susannah")]
        .into_iter()
        .collect(),
    });

    assert_eq!(
      result.unwrap(),
      ExecuteProcessResult {
        stdout: as_bytes(""),
        stderr: as_bytes(""),
        exit_code: 0,
        output_directory: TestDirectory::containing_roland().digest(),
      }
    )
  }

  fn run_command_locally(req: ExecuteProcessRequest) -> Result<ExecuteProcessResult, String> {
    run_command_locally_in_dir(req)
  }

  fn run_command_locally_in_dir(
    req: ExecuteProcessRequest,
  ) -> Result<ExecuteProcessResult, String> {
    let store_dir = TempDir::new("store").unwrap();
    let pool = Arc::new(fs::ResettablePool::new("test-pool-".to_owned()));
    let store = fs::Store::local_only(store_dir.path(), pool.clone()).unwrap();
    let runner = super::CommandRunner {
      store: store,
      fs_pool: pool,
    };
    runner.run(req).wait()
  }

  fn find_bash() -> String {
    which("bash")
      .expect("No bash on PATH")
      .to_str()
      .expect("Path to bash not unicode")
      .to_owned()
  }

  fn which(executable: &str) -> Option<PathBuf> {
    if let Some(paths) = env::var_os("PATH") {
      for path in env::split_paths(&paths) {
        let executable_path = path.join(executable);
        if is_executable(&executable_path) {
          return Some(executable_path);
        }
      }
    }
    None
  }

  fn is_executable(path: &Path) -> bool {
    std::fs::metadata(path)
      .map(|meta| meta.permissions().mode() & 0o100 == 0o100)
      .unwrap_or(false)
  }
}
<|endoftext|>


<commit_before>use crate::{
    cli::{print_error, Command},
    config, convert, open, plugins, serve, upgrade,
    util::dirs,
};
use anyhow::{anyhow, bail, Result};
use rustyline::error::ReadlineError;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(
    about = "Stencila command line tool",
    setting = structopt::clap::AppSettings::NoBinaryName,
    setting = structopt::clap::AppSettings::ColoredHelp,
    setting = structopt::clap::AppSettings::VersionlessSubcommands
)]
pub struct Line {
    #[structopt(subcommand)]
    pub command: Command,
}

/// Run the interactive REPL
#[tracing::instrument]
pub async fn run(prefix: &Vec<String>, config: &config::Config) -> Result<()> {
    let history_file = dirs::config(true)?.join("history.txt");

    let mut rl = editor::new();
    if rl.load_history(&history_file).is_err() {
        tracing::debug!("No previous history found")
    }

    let mut prefix = prefix.clone();
    let mut config = config.clone();

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(line) => {
                rl.add_history_entry(&line);

                let mut args = line
                    .split_whitespace()
                    .map(str::to_string)
                    .collect::<Vec<String>>();

                if let Some(first) = line.trim_start().chars().nth(0) {
                    if first == '~' {
                        println!("Command prefix is {:?}", prefix);
                        continue;
                    } else if first == '<' {
                        prefix = args[1..].into();
                        println!("Set command prefix to {:?}", prefix);
                        continue;
                    } else if first == '>' {
                        prefix.clear();
                        println!("Cleared command prefix");
                        continue;
                    } else if first == '?' {
                        args[0] = "help".into();
                    }
                };

                let args = [prefix.as_slice(), args.as_slice()].concat();
                match Line::clap().get_matches_from_safe(args) {
                    Ok(matches) => {
                        let Line { command } = Line::from_clap(&matches);
                        if let Err(error) = match command {
                            Command::Open(args) => open::cli::run(args).await,
                            Command::Convert(args) => convert::cli::run(args),
                            Command::Serve(args) => serve::cli::run(args, &config.serve).await,
                            Command::Plugins(args) => {
                                plugins::cli::run(args, &config.plugins).await
                            }
                            Command::Upgrade(args) => upgrade::cli::run(args, &config.upgrade),
                            Command::Config(args) => match config::cli::run(args, &config) {
                                Ok(config_changed) => {
                                    // Update the configuration (may have been changed by `set` and `reset`)
                                    config = config_changed;
                                    Ok(())
                                }
                                Err(err) => Err(err),
                            },
                        } {
                            print_error(error)
                        }
                    }
                    Err(error) => {
                        if error.kind == structopt::clap::ErrorKind::VersionDisplayed {
                            print!("{}", error)
                        } else if error.kind == structopt::clap::ErrorKind::HelpDisplayed
                            || error.kind == structopt::clap::ErrorKind::MissingArgumentOrSubcommand
                        {
                            // Remove the unnecessary command / version line at the start
                            let lines = format!("{}\n", error)
                                .to_string()
                                .lines()
                                .skip(1)
                                .map(str::to_string)
                                .collect::<Vec<String>>()
                                .join("\n");
                            print!("{}", lines)
                        } else {
                            tracing::debug!("{:?}", error.kind);
                            print_error(anyhow!(error))
                        }
                    }
                }
            }
            Err(ReadlineError::Interrupted) => {
                tracing::info!("Ctrl-C pressed, interrupting current command");
                // TODO
            }
            Err(ReadlineError::Eof) => {
                tracing::info!("Ctrl-D pressed, ending session");
                break;
            }
            Err(error) => bail!(error),
        }
    }
    rl.save_history(&history_file)?;

    Ok(())
}

mod editor {
    use ansi_term::Colour::{Blue, White, Yellow};
    use rustyline::{
        completion::{Completer, FilenameCompleter, Pair},
        config::OutputStreamType,
        highlight::{Highlighter, MatchingBracketHighlighter},
        hint::{Hinter, HistoryHinter},
        validate::{MatchingBracketValidator, Validator},
        validate::{ValidationContext, ValidationResult},
        CompletionType, Context, EditMode, Editor, Result,
    };
    use rustyline_derive::Helper;
    use std::borrow::Cow::{self, Owned};

    pub fn new() -> Editor<Helper> {
        let config = rustyline::Config::builder()
            .history_ignore_space(true)
            .max_history_size(1000)
            .completion_type(CompletionType::List)
            .edit_mode(EditMode::Emacs)
            .output_stream(OutputStreamType::Stdout)
            .build();

        let mut editor = Editor::with_config(config);

        let helper = Helper::new();
        editor.set_helper(Some(helper));

        editor
    }

    #[derive(Helper)]
    pub struct Helper {
        pub completer: FilenameCompleter,
        pub hinter: HistoryHinter,
        pub validator: MatchingBracketValidator,
        pub highlighter: MatchingBracketHighlighter,
    }

    impl Helper {
        pub fn new() -> Self {
            Helper {
                completer: FilenameCompleter::new(),
                hinter: HistoryHinter {},
                validator: MatchingBracketValidator::new(),
                highlighter: MatchingBracketHighlighter::new(),
            }
        }
    }

    /// Provides tab-completion candidates
    ///
    /// https://github.com/kkawakam/rustyline/blob/master/src/completion.rs
    impl Completer for Helper {
        type Candidate = Pair;

        fn complete(
            &self,
            line: &str,
            pos: usize,
            ctx: &Context<'_>,
        ) -> Result<(usize, Vec<Self::Candidate>)> {
            self.completer.complete(line, pos, ctx)
        }
    }

    /// Provides hints based on the current line
    ///
    /// See https://github.com/kkawakam/rustyline/blob/master/src/hint.rs
    impl Hinter for Helper {
        type Hint = String;

        // Takes the currently edited line with the cursor position and returns the string that should be
        // displayed or None if no hint is available for the text the user currently typed
        fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
            self.hinter.hint(line, pos, ctx)
        }
    }

    /// Determines whether the current buffer is a valid command or should continue.
    ///
    /// Will not validate unless brackets (round, square and curly) are balanced.
    impl Validator for Helper {
        fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
            self.validator.validate(ctx)
        }

        fn validate_while_typing(&self) -> bool {
            self.validator.validate_while_typing()
        }
    }

    /// Syntax highlighter
    ///
    /// Highlights brackets, prompt, hints and completion candidates.
    /// See https://github.com/kkawakam/rustyline/blob/master/src/highlight.rs
    impl Highlighter for Helper {
        fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
            self.highlighter.highlight(line, pos)
        }

        fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
            &'s self,
            prompt: &'p str,
            _default: bool,
        ) -> Cow<'b, str> {
            Owned(Blue.paint(prompt).to_string())
        }

        fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
            Owned(White.dimmed().paint(hint).to_string())
        }

        fn highlight_candidate<'c>(
            &self,
            candidate: &'c str,
            _completion: CompletionType,
        ) -> Cow<'c, str> {
            Owned(Yellow.dimmed().paint(candidate).to_string())
        }

        fn highlight_char(&self, line: &str, pos: usize) -> bool {
            self.highlighter.highlight_char(line, pos)
        }
    }
}
<commit_msg>chore(Interact): Linting<commit_after>use crate::{
    cli::{print_error, Command},
    config, convert, open, plugins, serve, upgrade,
    util::dirs,
};
use anyhow::{anyhow, bail, Result};
use rustyline::error::ReadlineError;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(
    about = "Stencila command line tool",
    setting = structopt::clap::AppSettings::NoBinaryName,
    setting = structopt::clap::AppSettings::ColoredHelp,
    setting = structopt::clap::AppSettings::VersionlessSubcommands
)]
pub struct Line {
    #[structopt(subcommand)]
    pub command: Command,
}

/// Run the interactive REPL
#[tracing::instrument]
pub async fn run(prefix: Vec<String>, config: &config::Config) -> Result<()> {
    let history_file = dirs::config(true)?.join("history.txt");

    let mut rl = editor::new();
    if rl.load_history(&history_file).is_err() {
        tracing::debug!("No previous history found")
    }

    let mut prefix = prefix.clone();
    let mut config = config.clone();

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(line) => {
                rl.add_history_entry(&line);

                let mut args = line
                    .split_whitespace()
                    .map(str::to_string)
                    .collect::<Vec<String>>();

                if let Some(first) = line.trim_start().chars().next() {
                    if first == '~' {
                        println!("Command prefix is {:?}", prefix);
                        continue;
                    } else if first == '<' {
                        prefix = args[1..].into();
                        println!("Set command prefix to {:?}", prefix);
                        continue;
                    } else if first == '>' {
                        prefix.clear();
                        println!("Cleared command prefix");
                        continue;
                    } else if first == '?' {
                        args[0] = "help".into();
                    }
                };

                let args = [prefix.as_slice(), args.as_slice()].concat();
                match Line::clap().get_matches_from_safe(args) {
                    Ok(matches) => {
                        let Line { command } = Line::from_clap(&matches);
                        if let Err(error) = match command {
                            Command::Open(args) => open::cli::run(args).await,
                            Command::Convert(args) => convert::cli::run(args),
                            Command::Serve(args) => serve::cli::run(args, &config.serve).await,
                            Command::Plugins(args) => {
                                plugins::cli::run(args, &config.plugins).await
                            }
                            Command::Upgrade(args) => upgrade::cli::run(args, &config.upgrade),
                            Command::Config(args) => match config::cli::run(args, &config) {
                                Ok(config_changed) => {
                                    // Update the configuration (may have been changed by `set` and `reset`)
                                    config = config_changed;
                                    Ok(())
                                }
                                Err(err) => Err(err),
                            },
                        } {
                            print_error(error)
                        }
                    }
                    Err(error) => {
                        if error.kind == structopt::clap::ErrorKind::VersionDisplayed {
                            print!("{}", error)
                        } else if error.kind == structopt::clap::ErrorKind::HelpDisplayed
                            || error.kind == structopt::clap::ErrorKind::MissingArgumentOrSubcommand
                        {
                            // Remove the unnecessary command / version line at the start
                            let lines = format!("{}\n", error)
                                .to_string()
                                .lines()
                                .skip(1)
                                .map(str::to_string)
                                .collect::<Vec<String>>()
                                .join("\n");
                            print!("{}", lines)
                        } else {
                            tracing::debug!("{:?}", error.kind);
                            print_error(anyhow!(error))
                        }
                    }
                }
            }
            Err(ReadlineError::Interrupted) => {
                tracing::info!("Ctrl-C pressed, interrupting current command");
                // TODO
            }
            Err(ReadlineError::Eof) => {
                tracing::info!("Ctrl-D pressed, ending session");
                break;
            }
            Err(error) => bail!(error),
        }
    }
    rl.save_history(&history_file)?;

    Ok(())
}

mod editor {
    use ansi_term::Colour::{Blue, White, Yellow};
    use rustyline::{
        completion::{Completer, FilenameCompleter, Pair},
        config::OutputStreamType,
        highlight::{Highlighter, MatchingBracketHighlighter},
        hint::{Hinter, HistoryHinter},
        validate::{MatchingBracketValidator, Validator},
        validate::{ValidationContext, ValidationResult},
        CompletionType, Context, EditMode, Editor, Result,
    };
    use rustyline_derive::Helper;
    use std::borrow::Cow::{self, Owned};

    pub fn new() -> Editor<Helper> {
        let config = rustyline::Config::builder()
            .history_ignore_space(true)
            .max_history_size(1000)
            .completion_type(CompletionType::List)
            .edit_mode(EditMode::Emacs)
            .output_stream(OutputStreamType::Stdout)
            .build();

        let mut editor = Editor::with_config(config);

        let helper = Helper::new();
        editor.set_helper(Some(helper));

        editor
    }

    #[derive(Helper)]
    pub struct Helper {
        pub completer: FilenameCompleter,
        pub hinter: HistoryHinter,
        pub validator: MatchingBracketValidator,
        pub highlighter: MatchingBracketHighlighter,
    }

    impl Helper {
        pub fn new() -> Self {
            Helper {
                completer: FilenameCompleter::new(),
                hinter: HistoryHinter {},
                validator: MatchingBracketValidator::new(),
                highlighter: MatchingBracketHighlighter::new(),
            }
        }
    }

    /// Provides tab-completion candidates
    ///
    /// https://github.com/kkawakam/rustyline/blob/master/src/completion.rs
    impl Completer for Helper {
        type Candidate = Pair;

        fn complete(
            &self,
            line: &str,
            pos: usize,
            ctx: &Context<'_>,
        ) -> Result<(usize, Vec<Self::Candidate>)> {
            self.completer.complete(line, pos, ctx)
        }
    }

    /// Provides hints based on the current line
    ///
    /// See https://github.com/kkawakam/rustyline/blob/master/src/hint.rs
    impl Hinter for Helper {
        type Hint = String;

        // Takes the currently edited line with the cursor position and returns the string that should be
        // displayed or None if no hint is available for the text the user currently typed
        fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
            self.hinter.hint(line, pos, ctx)
        }
    }

    /// Determines whether the current buffer is a valid command or should continue.
    ///
    /// Will not validate unless brackets (round, square and curly) are balanced.
    impl Validator for Helper {
        fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
            self.validator.validate(ctx)
        }

        fn validate_while_typing(&self) -> bool {
            self.validator.validate_while_typing()
        }
    }

    /// Syntax highlighter
    ///
    /// Highlights brackets, prompt, hints and completion candidates.
    /// See https://github.com/kkawakam/rustyline/blob/master/src/highlight.rs
    impl Highlighter for Helper {
        fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
            self.highlighter.highlight(line, pos)
        }

        fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
            &'s self,
            prompt: &'p str,
            _default: bool,
        ) -> Cow<'b, str> {
            Owned(Blue.paint(prompt).to_string())
        }

        fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
            Owned(White.dimmed().paint(hint).to_string())
        }

        fn highlight_candidate<'c>(
            &self,
            candidate: &'c str,
            _completion: CompletionType,
        ) -> Cow<'c, str> {
            Owned(Yellow.dimmed().paint(candidate).to_string())
        }

        fn highlight_char(&self, line: &str, pos: usize) -> bool {
            self.highlighter.highlight_char(line, pos)
        }
    }
}
<|endoftext|>


<commit_before>#![crate_id = "seax-svm"]
#![crate_type="lib"]

//! Contains the Seax Virtual Machine (SVM) and miscellaneous
//! support code.

#![feature(box_syntax)]
#![allow(dead_code)]

pub mod svm {

    use svm::slist::List;
    use svm::slist::Stack;

    /// Singly-linked list and stack implementations.
    pub mod slist {

        use svm::slist::List::{Cons,Nil};
        use std::fmt::Show;

        /// A stack implementation wrapping a List<T>
        pub struct Stack<T> {
            /// The head item of the list.
            head: Box<List<T>>
        }

        impl<T> Stack<T> {
            /// Push an item to the top of the stack, returning a new stack
            pub fn push(self, it: T) -> Stack<T> {
                Stack { head: Box::new(self.head.prepend(it)) }
            }

            /// Peak at the top item of the stack.
            ///
            /// Returns Some<T> if there is an item on top of the stack,
            /// and None if the stack is empty.
            pub fn peek(&self) -> Option<&T> {
                match *self.head {
                    Nil => None,
                    Cons(ref it,_) => Some(it)
                }
            }

            /// Returns an empty stack.
            pub fn empty() -> Stack<T> {
                Stack { head: box Nil }
            }

            /// Wraps a list into a stack.
            pub fn new(l: List<T>) -> Stack<T> {
                Stack { head: box l }
            }
        }

        /// Singly-linked cons list.
        ///
        /// This is used internally to represent list primitives in the machine.
        pub enum List<T> {
            Cons(T, Box<List<T>>),
            Nil,
        }

        /// Public implementation for List.
        impl<T> List<T> {

            /// Creates a new empty list
            pub fn new() -> List<T> {
                Nil
            }

            /// Prepends the given item to the list
            pub fn prepend(self, it: T) -> List<T> {
                Cons(it, box self)
            }

            /// Returns the length of the list
            pub fn length (&self) -> i32 {
                match *self {
                    Cons(_, ref tail) => 1 + tail.length(),
                    Nil => 0
                }
            }
        }

        impl<T> List<T> where T: Show {
            /// Return a string representation of the list
            fn to_string(&self) -> String {
                match *self {
                    Cons(ref head, ref tail) => format!("({:?}, {})", head, tail.to_string()),
                    Nil => format!("nil")
                }
            }

        }

       macro_rules! list(
            ( $e:expr, $($rest:expr),+ ) => ( Cons($e, Box::new(list!( $( $rest ),+ )) ));
            ( $e:expr ) => ( Cons($e, Box::new(Nil)) );
            () => ( @Empty )
        );

        #[cfg(test)]
        mod tests {
            use super::{List, Stack};
            use super::List::{Cons,Nil};

            #[test]
            fn test_list_length() {
                let l: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
                assert_eq!(l.length(), 3);
            }

            #[test]
            fn test_list_to_string() {
                let l: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
                assert_eq!(l.to_string(), "(1i32, (2i32, (3i32, nil)))");
            }

            #[test]
            fn test_stack_peek() {
                let s: Stack<i32> = Stack::new(Cons(1, Box::new(Cons(2, Box::new(Nil)))));
                assert_eq!(s.peek(), Some(&1));
            }

            #[test]
            fn test_stack_push() {
                let mut s: Stack<i32> = Stack::empty();
                assert_eq!(s.peek(), None);
                s = s.push(1);
                assert_eq!(s.peek(), Some(&1));
                s = s.push(6);
                assert_eq!(s.peek(), Some(&6));
            }

            #[test]
            fn test_stack_pop() {
                let mut s: Stack<i32> = Stack::empty();
                assert_eq!(s.peek(), None);
                s = s.push(1);
                assert_eq!(s.peek(), Some(&1));
                s = s.push(6);
                assert_eq!(s.peek(), Some(&6));
            }

            #[test]
            fn test_list_macro() {
                let l: List<i32> = list!(1i32, 2i32, 3i32);
                assert_eq!(l.to_string(), "(1i32, (2i32, (3i32, nil)))")
            }
        }
    }

    /// SVM item types
    pub enum SVMCell {
        Atom,
        ListCell(Box<List<SVMCell>>)
    }

    pub enum Atom {
        AtomUInt(usize),
        AtomSInt(isize),
        AtomFloat(f64),
        AtomStr(String)
    }

    /// SVM instruction types
    pub enum SVMInstruction {
        InstNIL,
        InstLDC(Atom),
        InstLD,
        InstLDF,
        InstJOIN,
        InstAP,
        InstRET,
        InstDUM,
        InstRAP
    }

    /// Represents a SVM machine state
    pub struct State {
        stack: Stack<SVMCell>,
        env: Stack<SVMCell>,
        control: Stack<SVMCell>,
        dump: Stack<SVMCell>
    }

    impl State {

        /// Creates a new empty state
        fn new() -> State {
            State {
                stack: Stack::empty(),
                env: Stack::empty(),
                control: Stack::empty(),
                dump: Stack::empty()
            }
        }

        pub fn eval(self, inst: SVMInstruction) -> State {
            match inst {
                SVMInstruction::InstNIL => State {
                    stack: self.stack.push(SVMCell::ListCell(box List::new())),
                    env: self.env,
                    control: self.control,
                    dump: self.dump
                },
                _ => { unimplemented!() }
            }
        }

    }
}
<commit_msg>Attempt to fix build on CI...<commit_after>#![crate_id = "seax-svm"]
#![crate_type="lib"]
#![feature(box_syntax)]
#![allow(dead_code)]

/// Contains the Seax Virtual Machine (SVM) and miscellaneous
/// support code.
pub mod svm {

    use svm::slist::List;
    use svm::slist::Stack;

    /// Singly-linked list and stack implementations.
    pub mod slist {

        use svm::slist::List::{Cons,Nil};
        use std::fmt::Show;

        /// A stack implementation wrapping a List<T>
        pub struct Stack<T> {
            /// The head item of the list.
            head: Box<List<T>>
        }

        impl<T> Stack<T> {
            /// Push an item to the top of the stack, returning a new stack
            pub fn push(self, it: T) -> Stack<T> {
                Stack { head: Box::new(self.head.prepend(it)) }
            }

            /// Peak at the top item of the stack.
            ///
            /// Returns Some<T> if there is an item on top of the stack,
            /// and None if the stack is empty.
            pub fn peek(&self) -> Option<&T> {
                match *self.head {
                    Nil => None,
                    Cons(ref it,_) => Some(it)
                }
            }

            /// Returns an empty stack.
            pub fn empty() -> Stack<T> {
                Stack { head: box Nil }
            }

            /// Wraps a list into a stack.
            pub fn new(l: List<T>) -> Stack<T> {
                Stack { head: box l }
            }
        }

        /// Singly-linked cons list.
        ///
        /// This is used internally to represent list primitives in the machine.
        pub enum List<T> {
            Cons(T, Box<List<T>>),
            Nil,
        }

        /// Public implementation for List.
        impl<T> List<T> {

            /// Creates a new empty list
            pub fn new() -> List<T> {
                Nil
            }

            /// Prepends the given item to the list
            pub fn prepend(self, it: T) -> List<T> {
                Cons(it, box self)
            }

            /// Returns the length of the list
            pub fn length (&self) -> i32 {
                match *self {
                    Cons(_, ref tail) => 1 + tail.length(),
                    Nil => 0
                }
            }
        }

        impl<T> List<T> where T: Show {
            /// Return a string representation of the list
            fn to_string(&self) -> String {
                match *self {
                    Cons(ref head, ref tail) => format!("({:?}, {})", head, tail.to_string()),
                    Nil => format!("nil")
                }
            }

        }

       macro_rules! list(
            ( $e:expr, $($rest:expr),+ ) => ( Cons($e, Box::new(list!( $( $rest ),+ )) ));
            ( $e:expr ) => ( Cons($e, Box::new(Nil)) );
            () => ( @Empty )
        );

        #[cfg(test)]
        mod tests {
            use super::{List, Stack};
            use super::List::{Cons,Nil};

            #[test]
            fn test_list_length() {
                let l: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
                assert_eq!(l.length(), 3);
            }

            #[test]
            fn test_list_to_string() {
                let l: List<i32> = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
                assert_eq!(l.to_string(), "(1i32, (2i32, (3i32, nil)))");
            }

            #[test]
            fn test_stack_peek() {
                let s: Stack<i32> = Stack::new(Cons(1, Box::new(Cons(2, Box::new(Nil)))));
                assert_eq!(s.peek(), Some(&1));
            }

            #[test]
            fn test_stack_push() {
                let mut s: Stack<i32> = Stack::empty();
                assert_eq!(s.peek(), None);
                s = s.push(1);
                assert_eq!(s.peek(), Some(&1));
                s = s.push(6);
                assert_eq!(s.peek(), Some(&6));
            }

            #[test]
            fn test_stack_pop() {
                let mut s: Stack<i32> = Stack::empty();
                assert_eq!(s.peek(), None);
                s = s.push(1);
                assert_eq!(s.peek(), Some(&1));
                s = s.push(6);
                assert_eq!(s.peek(), Some(&6));
            }

            #[test]
            fn test_list_macro() {
                let l: List<i32> = list!(1i32, 2i32, 3i32);
                assert_eq!(l.to_string(), "(1i32, (2i32, (3i32, nil)))")
            }
        }
    }

    /// SVM item types
    pub enum SVMCell {
        Atom,
        ListCell(Box<List<SVMCell>>)
    }

    pub enum Atom {
        AtomUInt(usize),
        AtomSInt(isize),
        AtomFloat(f64),
        AtomStr(String)
    }

    /// SVM instruction types
    pub enum SVMInstruction {
        InstNIL,
        InstLDC(Atom),
        InstLD,
        InstLDF,
        InstJOIN,
        InstAP,
        InstRET,
        InstDUM,
        InstRAP
    }

    /// Represents a SVM machine state
    pub struct State {
        stack: Stack<SVMCell>,
        env: Stack<SVMCell>,
        control: Stack<SVMCell>,
        dump: Stack<SVMCell>
    }

    impl State {

        /// Creates a new empty state
        fn new() -> State {
            State {
                stack: Stack::empty(),
                env: Stack::empty(),
                control: Stack::empty(),
                dump: Stack::empty()
            }
        }

        pub fn eval(self, inst: SVMInstruction) -> State {
            match inst {
                SVMInstruction::InstNIL => State {
                    stack: self.stack.push(SVMCell::ListCell(box List::new())),
                    env: self.env,
                    control: self.control,
                    dump: self.dump
                },
                _ => { unimplemented!() }
            }
        }

    }
}
<|endoftext|>


<commit_before>//! Concrete error types for all operations which may be invalid in a certain const context.

use rustc_errors::{struct_span_err, Applicability};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_session::config::nightly_options;
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};

use super::ConstCx;

/// Emits an error if `op` is not allowed in the given const context.
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) {
    debug!("illegal_op: op={:?}", op);

    let gate = match op.status_in_item(ccx) {
        Status::Allowed => return,

        Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
            let unstable_in_stable = ccx.is_const_stable_const_fn()
                && !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate);

            if unstable_in_stable {
                ccx.tcx.sess
                    .struct_span_err(span, &format!("`#[feature({})]` cannot be depended on in a const-stable function", gate.as_str()))
                    .span_suggestion(
                        ccx.body.span,
                        "if it is not part of the public API, make this function unstably const",
                        concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
                        Applicability::HasPlaceholders,
                    )
                    .help("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
                    .emit();
            }

            return;
        }

        Status::Unstable(gate) => Some(gate),
        Status::Forbidden => None,
    };

    if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
        ccx.tcx.sess.miri_unleashed_feature(span, gate);
        return;
    }

    op.emit_error(ccx, span);
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
    Allowed,
    Unstable(Symbol),
    Forbidden,
}

/// An operation that is not *always* allowed in a const context.
pub trait NonConstOp: std::fmt::Debug {
    /// Returns an enum indicating whether this operation is allowed within the given item.
    fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
        Status::Forbidden
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0019,
            "{} contains unimplemented expression type",
            ccx.const_kind()
        );

        if let Status::Unstable(gate) = self.status_in_item(ccx) {
            if !ccx.tcx.features().enabled(gate) && nightly_options::is_nightly_build() {
                err.help(&format!("add `#![feature({})]` to the crate attributes to enable", gate));
            }
        }

        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "A function call isn't allowed in the const's initialization expression \
                      because the expression's value must be known at compile-time.",
            );
            err.note(
                "Remember: you can't use a function call inside a const's initialization \
                      expression! However, you can use it anywhere else.",
            );
        }
        err.emit();
    }
}

/// A function call where the callee is a pointer.
#[derive(Debug)]
pub struct FnCallIndirect;
impl NonConstOp for FnCallIndirect {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err =
            ccx.tcx.sess.struct_span_err(span, "function pointers are not allowed in const fn");
        err.emit();
    }
}

/// A function call where the callee is not marked as `const`.
#[derive(Debug)]
pub struct FnCallNonConst(pub DefId);
impl NonConstOp for FnCallNonConst {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0015,
            "calls in {}s are limited to constant functions, \
             tuple structs and tuple variants",
            ccx.const_kind(),
        );
        err.emit();
    }
}

/// A call to a `#[unstable]` const fn or `#[rustc_const_unstable]` function.
///
/// Contains the name of the feature that would allow the use of this function.
#[derive(Debug)]
pub struct FnCallUnstable(pub DefId, pub Symbol);
impl NonConstOp for FnCallUnstable {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let FnCallUnstable(def_id, feature) = *self;

        let mut err = ccx.tcx.sess.struct_span_err(
            span,
            &format!("`{}` is not yet stable as a const fn", ccx.tcx.def_path_str(def_id)),
        );
        if nightly_options::is_nightly_build() {
            err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feature));
        }
        err.emit();
    }
}

#[derive(Debug)]
pub struct HeapAllocation;
impl NonConstOp for HeapAllocation {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0010,
            "allocations are not allowed in {}s",
            ccx.const_kind()
        );
        err.span_label(span, format!("allocation not allowed in {}s", ccx.const_kind()));
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "The value of statics and constants must be known at compile time, \
                 and they live for the entire lifetime of a program. Creating a boxed \
                 value allocates memory on the heap at runtime, and therefore cannot \
                 be done at compile time.",
            );
        }
        err.emit();
    }
}

#[derive(Debug)]
pub struct InlineAsm;
impl NonConstOp for InlineAsm {}

#[derive(Debug)]
pub struct LiveDrop {
    pub dropped_at: Option<Span>,
}
impl NonConstOp for LiveDrop {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut diagnostic = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0493,
            "destructors cannot be evaluated at compile-time"
        );
        diagnostic.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
        if let Some(span) = self.dropped_at {
            diagnostic.span_label(span, "value is dropped here");
        }
        diagnostic.emit();
    }
}

#[derive(Debug)]
pub struct CellBorrow;
impl NonConstOp for CellBorrow {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        struct_span_err!(
            ccx.tcx.sess,
            span,
            E0492,
            "cannot borrow a constant which may contain \
            interior mutability, create a static instead"
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct MutBorrow;
impl NonConstOp for MutBorrow {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Forbid everywhere except in const fn with a feature gate
        if ccx.const_kind() == hir::ConstContext::ConstFn {
            Status::Unstable(sym::const_mut_refs)
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = if ccx.const_kind() == hir::ConstContext::ConstFn {
            feature_err(
                &ccx.tcx.sess.parse_sess,
                sym::const_mut_refs,
                span,
                &format!("mutable references are not allowed in {}s", ccx.const_kind()),
            )
        } else {
            let mut err = struct_span_err!(
                ccx.tcx.sess,
                span,
                E0764,
                "mutable references are not allowed in {}s",
                ccx.const_kind(),
            );
            err.span_label(span, format!("`&mut` is only allowed in `const fn`"));
            err
        };
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "References in statics and constants may only refer \
                      to immutable values.\n\n\
                      Statics are shared everywhere, and if they refer to \
                      mutable data one might violate memory safety since \
                      holding multiple mutable references to shared data \
                      is not allowed.\n\n\
                      If you really want global mutable state, try using \
                      static mut or a global UnsafeCell.",
            );
        }
        err.emit();
    }
}

// FIXME(ecstaticmorse): Unify this with `MutBorrow`. It has basically the same issues.
#[derive(Debug)]
pub struct MutAddressOf;
impl NonConstOp for MutAddressOf {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Forbid everywhere except in const fn with a feature gate
        if ccx.const_kind() == hir::ConstContext::ConstFn {
            Status::Unstable(sym::const_mut_refs)
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_mut_refs,
            span,
            &format!("`&raw mut` is not allowed in {}s", ccx.const_kind()),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct MutDeref;
impl NonConstOp for MutDeref {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_mut_refs)
    }
}

#[derive(Debug)]
pub struct Panic;
impl NonConstOp for Panic {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_panic)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_panic,
            span,
            &format!("panicking in {}s is unstable", ccx.const_kind()),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct RawPtrComparison;
impl NonConstOp for RawPtrComparison {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = ccx
            .tcx
            .sess
            .struct_span_err(span, "pointers cannot be reliably compared during const eval.");
        err.note(
            "see issue #53020 <https://github.com/rust-lang/rust/issues/53020> \
            for more information",
        );
        err.emit();
    }
}

#[derive(Debug)]
pub struct RawPtrDeref;
impl NonConstOp for RawPtrDeref {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_raw_ptr_deref)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_raw_ptr_deref,
            span,
            &format!("dereferencing raw pointers in {}s is unstable", ccx.const_kind(),),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct RawPtrToIntCast;
impl NonConstOp for RawPtrToIntCast {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_raw_ptr_to_usize_cast)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_raw_ptr_to_usize_cast,
            span,
            &format!("casting pointers to integers in {}s is unstable", ccx.const_kind(),),
        )
        .emit();
    }
}

/// An access to a (non-thread-local) `static`.
#[derive(Debug)]
pub struct StaticAccess;
impl NonConstOp for StaticAccess {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        if let hir::ConstContext::Static(_) = ccx.const_kind() {
            Status::Allowed
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0013,
            "{}s cannot refer to statics",
            ccx.const_kind()
        );
        err.help(
            "consider extracting the value of the `static` to a `const`, and referring to that",
        );
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "`static` and `const` variables can refer to other `const` variables. \
                    A `const` variable, however, cannot refer to a `static` variable.",
            );
            err.help("To fix this, the value can be extracted to a `const` and then used.");
        }
        err.emit();
    }
}

/// An access to a thread-local `static`.
#[derive(Debug)]
pub struct ThreadLocalAccess;
impl NonConstOp for ThreadLocalAccess {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        struct_span_err!(
            ccx.tcx.sess,
            span,
            E0625,
            "thread-local statics cannot be \
            accessed at compile-time"
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct UnionAccess;
impl NonConstOp for UnionAccess {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Union accesses are stable in all contexts except `const fn`.
        if ccx.const_kind() != hir::ConstContext::ConstFn {
            Status::Allowed
        } else {
            Status::Unstable(sym::const_fn_union)
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_fn_union,
            span,
            "unions in const fn are unstable",
        )
        .emit();
    }
}
<commit_msg>Return `true` if `check_const` emits an error<commit_after>//! Concrete error types for all operations which may be invalid in a certain const context.

use rustc_errors::{struct_span_err, Applicability};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_session::config::nightly_options;
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};

use super::ConstCx;

/// Emits an error and returns `true` if `op` is not allowed in the given const context.
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) -> bool {
    debug!("illegal_op: op={:?}", op);

    let gate = match op.status_in_item(ccx) {
        Status::Allowed => return false,

        Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
            let unstable_in_stable = ccx.is_const_stable_const_fn()
                && !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate);

            if unstable_in_stable {
                ccx.tcx.sess
                    .struct_span_err(
                        span,
                        &format!("const-stable function cannot use `#[feature({})]`", gate.as_str()),
                    )
                    .span_suggestion(
                        ccx.body.span,
                        "if it is not part of the public API, make this function unstably const",
                        concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
                        Applicability::HasPlaceholders,
                    )
                    .note("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
                    .emit();
            }

            return unstable_in_stable;
        }

        Status::Unstable(gate) => Some(gate),
        Status::Forbidden => None,
    };

    if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
        ccx.tcx.sess.miri_unleashed_feature(span, gate);
        return false;
    }

    op.emit_error(ccx, span);
    true
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
    Allowed,
    Unstable(Symbol),
    Forbidden,
}

/// An operation that is not *always* allowed in a const context.
pub trait NonConstOp: std::fmt::Debug {
    /// Returns an enum indicating whether this operation is allowed within the given item.
    fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
        Status::Forbidden
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0019,
            "{} contains unimplemented expression type",
            ccx.const_kind()
        );

        if let Status::Unstable(gate) = self.status_in_item(ccx) {
            if !ccx.tcx.features().enabled(gate) && nightly_options::is_nightly_build() {
                err.help(&format!("add `#![feature({})]` to the crate attributes to enable", gate));
            }
        }

        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "A function call isn't allowed in the const's initialization expression \
                      because the expression's value must be known at compile-time.",
            );
            err.note(
                "Remember: you can't use a function call inside a const's initialization \
                      expression! However, you can use it anywhere else.",
            );
        }
        err.emit();
    }
}

/// A function call where the callee is a pointer.
#[derive(Debug)]
pub struct FnCallIndirect;
impl NonConstOp for FnCallIndirect {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err =
            ccx.tcx.sess.struct_span_err(span, "function pointers are not allowed in const fn");
        err.emit();
    }
}

/// A function call where the callee is not marked as `const`.
#[derive(Debug)]
pub struct FnCallNonConst(pub DefId);
impl NonConstOp for FnCallNonConst {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0015,
            "calls in {}s are limited to constant functions, \
             tuple structs and tuple variants",
            ccx.const_kind(),
        );
        err.emit();
    }
}

/// A call to a `#[unstable]` const fn or `#[rustc_const_unstable]` function.
///
/// Contains the name of the feature that would allow the use of this function.
#[derive(Debug)]
pub struct FnCallUnstable(pub DefId, pub Symbol);
impl NonConstOp for FnCallUnstable {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let FnCallUnstable(def_id, feature) = *self;

        let mut err = ccx.tcx.sess.struct_span_err(
            span,
            &format!("`{}` is not yet stable as a const fn", ccx.tcx.def_path_str(def_id)),
        );
        if nightly_options::is_nightly_build() {
            err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feature));
        }
        err.emit();
    }
}

#[derive(Debug)]
pub struct HeapAllocation;
impl NonConstOp for HeapAllocation {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0010,
            "allocations are not allowed in {}s",
            ccx.const_kind()
        );
        err.span_label(span, format!("allocation not allowed in {}s", ccx.const_kind()));
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "The value of statics and constants must be known at compile time, \
                 and they live for the entire lifetime of a program. Creating a boxed \
                 value allocates memory on the heap at runtime, and therefore cannot \
                 be done at compile time.",
            );
        }
        err.emit();
    }
}

#[derive(Debug)]
pub struct InlineAsm;
impl NonConstOp for InlineAsm {}

#[derive(Debug)]
pub struct LiveDrop {
    pub dropped_at: Option<Span>,
}
impl NonConstOp for LiveDrop {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut diagnostic = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0493,
            "destructors cannot be evaluated at compile-time"
        );
        diagnostic.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
        if let Some(span) = self.dropped_at {
            diagnostic.span_label(span, "value is dropped here");
        }
        diagnostic.emit();
    }
}

#[derive(Debug)]
pub struct CellBorrow;
impl NonConstOp for CellBorrow {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        struct_span_err!(
            ccx.tcx.sess,
            span,
            E0492,
            "cannot borrow a constant which may contain \
            interior mutability, create a static instead"
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct MutBorrow;
impl NonConstOp for MutBorrow {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Forbid everywhere except in const fn with a feature gate
        if ccx.const_kind() == hir::ConstContext::ConstFn {
            Status::Unstable(sym::const_mut_refs)
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = if ccx.const_kind() == hir::ConstContext::ConstFn {
            feature_err(
                &ccx.tcx.sess.parse_sess,
                sym::const_mut_refs,
                span,
                &format!("mutable references are not allowed in {}s", ccx.const_kind()),
            )
        } else {
            let mut err = struct_span_err!(
                ccx.tcx.sess,
                span,
                E0764,
                "mutable references are not allowed in {}s",
                ccx.const_kind(),
            );
            err.span_label(span, format!("`&mut` is only allowed in `const fn`"));
            err
        };
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "References in statics and constants may only refer \
                      to immutable values.\n\n\
                      Statics are shared everywhere, and if they refer to \
                      mutable data one might violate memory safety since \
                      holding multiple mutable references to shared data \
                      is not allowed.\n\n\
                      If you really want global mutable state, try using \
                      static mut or a global UnsafeCell.",
            );
        }
        err.emit();
    }
}

// FIXME(ecstaticmorse): Unify this with `MutBorrow`. It has basically the same issues.
#[derive(Debug)]
pub struct MutAddressOf;
impl NonConstOp for MutAddressOf {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Forbid everywhere except in const fn with a feature gate
        if ccx.const_kind() == hir::ConstContext::ConstFn {
            Status::Unstable(sym::const_mut_refs)
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_mut_refs,
            span,
            &format!("`&raw mut` is not allowed in {}s", ccx.const_kind()),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct MutDeref;
impl NonConstOp for MutDeref {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_mut_refs)
    }
}

#[derive(Debug)]
pub struct Panic;
impl NonConstOp for Panic {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_panic)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_panic,
            span,
            &format!("panicking in {}s is unstable", ccx.const_kind()),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct RawPtrComparison;
impl NonConstOp for RawPtrComparison {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = ccx
            .tcx
            .sess
            .struct_span_err(span, "pointers cannot be reliably compared during const eval.");
        err.note(
            "see issue #53020 <https://github.com/rust-lang/rust/issues/53020> \
            for more information",
        );
        err.emit();
    }
}

#[derive(Debug)]
pub struct RawPtrDeref;
impl NonConstOp for RawPtrDeref {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_raw_ptr_deref)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_raw_ptr_deref,
            span,
            &format!("dereferencing raw pointers in {}s is unstable", ccx.const_kind(),),
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct RawPtrToIntCast;
impl NonConstOp for RawPtrToIntCast {
    fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
        Status::Unstable(sym::const_raw_ptr_to_usize_cast)
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_raw_ptr_to_usize_cast,
            span,
            &format!("casting pointers to integers in {}s is unstable", ccx.const_kind(),),
        )
        .emit();
    }
}

/// An access to a (non-thread-local) `static`.
#[derive(Debug)]
pub struct StaticAccess;
impl NonConstOp for StaticAccess {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        if let hir::ConstContext::Static(_) = ccx.const_kind() {
            Status::Allowed
        } else {
            Status::Forbidden
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        let mut err = struct_span_err!(
            ccx.tcx.sess,
            span,
            E0013,
            "{}s cannot refer to statics",
            ccx.const_kind()
        );
        err.help(
            "consider extracting the value of the `static` to a `const`, and referring to that",
        );
        if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
            err.note(
                "`static` and `const` variables can refer to other `const` variables. \
                    A `const` variable, however, cannot refer to a `static` variable.",
            );
            err.help("To fix this, the value can be extracted to a `const` and then used.");
        }
        err.emit();
    }
}

/// An access to a thread-local `static`.
#[derive(Debug)]
pub struct ThreadLocalAccess;
impl NonConstOp for ThreadLocalAccess {
    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        struct_span_err!(
            ccx.tcx.sess,
            span,
            E0625,
            "thread-local statics cannot be \
            accessed at compile-time"
        )
        .emit();
    }
}

#[derive(Debug)]
pub struct UnionAccess;
impl NonConstOp for UnionAccess {
    fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
        // Union accesses are stable in all contexts except `const fn`.
        if ccx.const_kind() != hir::ConstContext::ConstFn {
            Status::Allowed
        } else {
            Status::Unstable(sym::const_fn_union)
        }
    }

    fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
        feature_err(
            &ccx.tcx.sess.parse_sess,
            sym::const_fn_union,
            span,
            "unions in const fn are unstable",
        )
        .emit();
    }
}
<|endoftext|>