Merge branch 'main' into pr/320

This commit is contained in:
Shunsuke Shibayama 2023-06-09 22:04:44 +09:00
commit 924b22a171
91 changed files with 1836 additions and 1027 deletions

View file

@ -2,7 +2,7 @@
name = "els"
description = "An Erg compiler frontend for IDEs, implements LSP."
documentation = "http://docs.rs/els"
version = "0.1.26-nightly.4"
version = "0.1.27-nightly.0"
authors.workspace = true
license.workspace = true
edition.workspace = true

View file

@ -3,10 +3,11 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::CompletionResponse;
use serde_json::Value;
use erg_common::config::{ErgConfig, Input};
use erg_common::config::ErgConfig;
use erg_common::dict::Dict;
use erg_common::env::erg_pystd_path;
use erg_common::impl_u8_enum;
use erg_common::io::Input;
use erg_common::python_util::BUILTIN_PYTHON_MODS;
use erg_common::set::Set;
use erg_common::shared::AtomicShared;

View file

@ -19,7 +19,7 @@ use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn get_ast(&self, uri: &NormalizedUrl) -> Option<Module> {
let code = self.file_cache.get_entire_code(uri).ok()?;
Parser::parse(code).ok()
Parser::parse(code).ok().map(|artifact| artifact.ast)
}
pub(crate) fn check_file<S: Into<String>>(

View file

@ -294,9 +294,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let src = self.file_cache.get_entire_code(&uri)?;
let mut builder = ASTBuilder::new(self.cfg.inherit(path));
let result = match builder.build_without_desugaring(src) {
Ok(ast) => {
Ok(artifact) => {
let mut state = ASTSemanticState::new();
let tokens = state.enumerate_tokens(ast);
let tokens = state.enumerate_tokens(artifact.ast);
Some(SemanticTokensResult::Tokens(tokens))
}
Err(_) => None,

View file

@ -2,24 +2,18 @@
//!
//! コマンドオプション(パーサー)を定義する
use std::env;
use std::ffi::OsStr;
use std::fmt;
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, IsTerminal, Read, Write};
use std::path::{Path, PathBuf};
use std::io::{stdin, IsTerminal, Read};
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use crate::consts::{ERG_MODE, EXPERIMENTAL_MODE};
use crate::env::{erg_py_external_lib_path, erg_pystd_path, erg_std_path, python_site_packages};
use crate::help_messages::{command_message, mode_message, OPTIONS};
use crate::io::{Input, Output};
use crate::levenshtein::get_similar_name;
use crate::pathutil::add_postfix_foreach;
use crate::python_util::{detect_magic_number, get_python_version, get_sys_path, PythonVersion};
use crate::random::random;
use crate::normalize_path;
use crate::python_util::{detect_magic_number, get_python_version, PythonVersion};
use crate::serialize::{get_magic_num_from_bytes, get_ver_from_magic_num};
use crate::stdin::GLOBAL_STDIN;
use crate::{normalize_path, power_assert, read_file};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErgMode {
@ -77,671 +71,6 @@ impl fmt::Display for ErgMode {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DummyStdin {
pub name: String,
current_line: usize,
lines: Vec<String>,
}
impl DummyStdin {
pub fn new(name: String, lines: Vec<String>) -> Self {
Self {
name,
current_line: 0,
lines,
}
}
pub fn read_line(&mut self) -> String {
let mut stdout = std::io::stdout();
if self.current_line >= self.lines.len() {
stdout.write_all("\n".as_bytes()).unwrap();
stdout.flush().unwrap();
// workaround: https://github.com/erg-lang/erg/issues/399
return "exit()".to_string();
}
let mut line = self.lines[self.current_line].clone();
self.current_line += 1;
line.push('\n');
stdout.write_all(line.as_bytes()).unwrap();
stdout.flush().unwrap();
line
}
pub fn reread_lines(&self, ln_begin: usize, ln_end: usize) -> Vec<String> {
self.lines[ln_begin - 1..=ln_end - 1].to_vec()
}
pub fn reread(&self) -> Option<String> {
self.lines.get(self.current_line).cloned()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputKind {
File(PathBuf),
REPL,
DummyREPL(DummyStdin),
/// same content as cfg.command
Pipe(String),
/// from command option | eval
Str(String),
Dummy,
}
impl InputKind {
pub const fn is_repl(&self) -> bool {
matches!(self, Self::REPL | Self::DummyREPL(_))
}
pub fn path(&self) -> Option<&Path> {
match self {
Self::File(path) => Some(path),
_ => None,
}
}
pub fn enclosed_name(&self) -> &str {
match self {
Self::File(filename) => filename.to_str().unwrap_or("_"),
Self::REPL | Self::DummyREPL(_) | Self::Pipe(_) => "<stdin>",
Self::Str(_) => "<string>",
Self::Dummy => "<dummy>",
}
}
pub fn dir(&self) -> PathBuf {
if let Self::File(path) = self {
let mut path = path.clone();
path.pop();
if path.parent().is_none() {
PathBuf::from(".")
} else {
path
}
} else {
PathBuf::from(".")
}
}
pub fn project_root(&self) -> Option<PathBuf> {
if let Self::File(path) = self {
let mut parent = path.clone();
while parent.pop() {
if parent.join("package.er").exists() {
return Some(parent);
}
}
None
} else {
None
}
}
}
/// Since input is not always only from files
/// Unify operations with `Input`
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Input {
pub(crate) kind: InputKind,
/// Unique id to avoid file name collision
id: u64,
}
impl From<PathBuf> for Input {
fn from(path: PathBuf) -> Self {
Self::file(path)
}
}
impl From<&Path> for Input {
fn from(path: &Path) -> Self {
Self::file(path.to_path_buf())
}
}
impl Input {
pub const fn new(kind: InputKind, id: u64) -> Self {
Self { kind, id }
}
pub fn file(path: PathBuf) -> Self {
Self::new(InputKind::File(path), random())
}
pub fn pipe(src: String) -> Self {
Self::new(InputKind::Pipe(src), random())
}
pub fn str(src: String) -> Self {
Self::new(InputKind::Str(src), random())
}
pub fn repl() -> Self {
Self::new(InputKind::REPL, random())
}
pub fn dummy() -> Self {
Self::new(InputKind::Dummy, random())
}
pub fn dummy_repl(stdin: DummyStdin) -> Self {
Self::new(InputKind::DummyREPL(stdin), random())
}
pub const fn is_repl(&self) -> bool {
self.kind.is_repl()
}
pub const fn id(&self) -> u64 {
self.id
}
pub fn path(&self) -> Option<&Path> {
self.kind.path()
}
pub fn dir(&self) -> PathBuf {
self.kind.dir()
}
pub fn project_root(&self) -> Option<PathBuf> {
self.kind.project_root()
}
pub fn enclosed_name(&self) -> &str {
self.kind.enclosed_name()
}
pub fn lineno(&self) -> usize {
GLOBAL_STDIN.lineno()
}
pub fn block_begin(&self) -> usize {
GLOBAL_STDIN.block_begin()
}
pub fn set_block_begin(&self) {
GLOBAL_STDIN.set_block_begin(self.lineno())
}
pub fn insert_whitespace(&self, whitespace: &str) {
GLOBAL_STDIN.insert_whitespace(whitespace);
}
pub fn set_indent(&self, indent: usize) {
GLOBAL_STDIN.set_indent(indent);
}
pub fn file_stem(&self) -> String {
match &self.kind {
InputKind::File(filename) => format!(
"{}_{}",
filename.file_stem().and_then(|f| f.to_str()).unwrap_or("_"),
self.id
),
InputKind::REPL | InputKind::Pipe(_) => format!("stdin_{}", self.id),
InputKind::DummyREPL(stdin) => format!("stdin_{}_{}", stdin.name, self.id),
InputKind::Str(_) => format!("string_{}", self.id),
InputKind::Dummy => "dummy".to_string(),
}
}
pub fn full_path(&self) -> PathBuf {
match &self.kind {
InputKind::File(filename) => {
PathBuf::from(format!("{}_{}", filename.display(), self.id))
}
_ => PathBuf::from(self.file_stem()),
}
}
pub fn filename(&self) -> String {
match &self.kind {
InputKind::File(filename) => format!(
"{}_{}",
filename.file_name().and_then(|f| f.to_str()).unwrap_or("_"),
self.id
),
_ => self.file_stem(),
}
}
pub fn unescaped_file_stem(&self) -> &str {
match &self.kind {
InputKind::File(filename) => {
filename.file_stem().and_then(|f| f.to_str()).unwrap_or("_")
}
InputKind::REPL | InputKind::Pipe(_) => "stdin",
InputKind::DummyREPL(_stdin) => "stdin",
InputKind::Str(_) => "string",
InputKind::Dummy => "dummy",
}
}
pub fn unescaped_filename(&self) -> &str {
match &self.kind {
InputKind::File(filename) => {
filename.file_name().and_then(|f| f.to_str()).unwrap_or("_")
}
InputKind::REPL | InputKind::Pipe(_) => "stdin",
InputKind::DummyREPL(_stdin) => "stdin",
InputKind::Str(_) => "string",
InputKind::Dummy => "dummy",
}
}
pub fn unescaped_path(&self) -> &Path {
match &self.kind {
InputKind::File(filename) => filename.as_path(),
InputKind::REPL | InputKind::Pipe(_) => Path::new("stdin"),
InputKind::DummyREPL(_stdin) => Path::new("stdin"),
InputKind::Str(_) => Path::new("string"),
InputKind::Dummy => Path::new("dummy"),
}
}
pub fn module_name(&self) -> String {
match &self.kind {
InputKind::File(filename) => {
let file_stem = if filename.file_stem() == Some(OsStr::new("__init__")) {
filename.parent().and_then(|p| p.file_stem())
} else {
filename.file_stem()
};
file_stem
.and_then(|f| f.to_str())
.unwrap_or("_")
.to_string()
}
InputKind::REPL | InputKind::Pipe(_) => "<stdin>".to_string(),
InputKind::DummyREPL(stdin) => stdin.name.clone(),
InputKind::Str(_) => "<string>".to_string(),
InputKind::Dummy => "<dummy>".to_string(),
}
}
pub fn read(&mut self) -> String {
match &mut self.kind {
InputKind::File(filename) => {
let file = match File::open(&filename) {
Ok(f) => f,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
let lossy = filename.to_str().unwrap().to_string();
println!("cannot open '{lossy}': [Errno {code}] {e}",);
process::exit(code);
}
};
match read_file(file) {
Ok(s) => s,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
println!(
"cannot read '{}': [Errno {code}] {e}",
filename.to_string_lossy()
);
process::exit(code);
}
}
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.read(),
InputKind::DummyREPL(dummy) => dummy.read_line(),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn try_read(&mut self) -> std::io::Result<String> {
match &mut self.kind {
InputKind::File(filename) => {
let file = File::open(filename)?;
read_file(file)
}
InputKind::Pipe(s) | InputKind::Str(s) => Ok(s.clone()),
InputKind::REPL => Ok(GLOBAL_STDIN.read()),
InputKind::DummyREPL(dummy) => Ok(dummy.read_line()),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn read_non_dummy(&self) -> String {
match &self.kind {
InputKind::File(filename) => {
let file = match File::open(filename) {
Ok(f) => f,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
let lossy = filename.to_str().unwrap().to_string();
println!("cannot open '{lossy}': [Errno {code}] {e}",);
process::exit(code);
}
};
match read_file(file) {
Ok(s) => s,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
println!(
"cannot read '{}': [Errno {code}] {e}",
filename.to_string_lossy()
);
process::exit(code);
}
}
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.read(),
InputKind::Dummy | InputKind::DummyREPL(_) => panic!("cannot read from a dummy file"),
}
}
pub fn reread_lines(&self, ln_begin: usize, ln_end: usize) -> Vec<String> {
power_assert!(ln_begin, >=, 1);
match &self.kind {
InputKind::File(filename) => match File::open(filename) {
Ok(file) => {
let mut codes = vec![];
let mut lines = BufReader::new(file).lines().skip(ln_begin - 1);
for _ in ln_begin..=ln_end {
codes.push(lines.next().unwrap_or_else(|| Ok("".to_string())).unwrap());
}
codes
}
Err(_) => vec!["<file not found>".into()],
},
InputKind::Pipe(s) | InputKind::Str(s) => s.split('\n').collect::<Vec<_>>()
[ln_begin - 1..=ln_end - 1]
.iter()
.map(|s| s.to_string())
.collect(),
InputKind::REPL => {
if ln_begin == ln_end {
vec![GLOBAL_STDIN.reread()]
} else {
GLOBAL_STDIN.reread_lines(ln_begin, ln_end)
}
}
InputKind::DummyREPL(dummy) => dummy.reread_lines(ln_begin, ln_end),
InputKind::Dummy => panic!("cannot read lines from a dummy file"),
}
}
pub fn reread(&self) -> String {
match &self.kind {
InputKind::File(path) => {
let mut reader = BufReader::new(File::open(path).unwrap());
let mut buf = String::new();
reader.read_to_string(&mut buf).unwrap();
buf
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.reread().trim_end().to_owned(),
InputKind::DummyREPL(dummy) => dummy.reread().unwrap_or_default(),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn sys_path(&self) -> Result<Vec<PathBuf>, std::io::Error> {
get_sys_path(self.unescaped_path().parent())
}
/// resolution order:
/// 1. `{path/to}.er`
/// 2. `{path/to}/__init__.er`
fn resolve_local(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
let mut dir = self.dir();
dir.push(path);
dir.set_extension("er"); // {path/to}.er
let path = dir.canonicalize().or_else(|_| {
dir.pop(); // {path}
dir.push(path.iter().last().unwrap_or_default()); // {path/to}
dir.push("__init__.er"); // -> {path/to}/__init__.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
fn resolve_local_decl(&self, dir: PathBuf, path: &Path) -> Result<PathBuf, std::io::Error> {
self._resolve_local_decl(dir.clone(), path).or_else(|_| {
let path = add_postfix_foreach(path, ".d");
self._resolve_local_decl(dir, &path)
})
}
/// resolution order:
/// 1. `{path/to}.d.er`
/// 2. `{path/to}/__init__.d.er`
/// 3. `{path}/__pycache__/{to}.d.er`
/// 4. `{path/to}/__pycache__/__init__.d.er`
fn _resolve_local_decl(
&self,
mut dir: PathBuf,
path: &Path,
) -> Result<PathBuf, std::io::Error> {
let mut comps = path.components();
let last = comps
.next_back()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "path is empty"))?;
let last_path = Path::new(&last);
dir.push(comps);
dir.push(last_path);
dir.set_extension("d.er"); // {path/to}.d.er
let path = dir
.canonicalize()
.or_else(|_| {
dir.pop(); // {path/to}.d.er -> {path}
dir.push(last_path); // -> {path/to}
dir.push("__init__.d.er"); // -> {path/to}/__init__.d.er
dir.canonicalize()
})
.or_else(|_| {
dir.pop(); // -> {path/to}
dir.pop(); // -> {path}
dir.push("__pycache__"); // -> {path}/__pycache__
dir.push(last_path); // -> {path}/__pycache__/{to}
dir.set_extension("d.er"); // -> {path}/__pycache__/{to}.d.er
dir.canonicalize()
})
.or_else(|_| {
dir.pop(); // -> {path}/__pycache__
dir.pop(); // -> {path}
dir.push(last_path); // -> {path/to}
dir.push("__pycache__"); // -> {path/to}/__pycache__
dir.push("__init__.d.er"); // -> {path/to}/__pycache__/__init__.d.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
fn resolve_local_py(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
let mut dir = self.dir();
dir.push(path);
dir.set_extension("py");
let path = dir.canonicalize().or_else(|_| {
dir.pop();
dir.push(path);
dir.push("__init__.py"); // {path}/__init__.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
pub fn resolve_py(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
if ERG_MODE || path.starts_with("./") {
if let Ok(path) = self.resolve_local_py(path) {
return Ok(path);
}
}
for sys_path in self.sys_path()? {
let mut dir = sys_path;
dir.push(path);
dir.set_extension("py");
if dir.exists() {
return Ok(normalize_path(dir));
}
dir.pop();
dir.push(path);
dir.push("__init__.py");
if dir.exists() {
return Ok(normalize_path(dir));
}
if !EXPERIMENTAL_MODE {
break;
}
}
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("cannot find module `{}`", path.display()),
))
}
pub fn resolve_path(&self, path: &Path) -> Option<PathBuf> {
self.resolve_real_path(path)
.or_else(|| self.resolve_decl_path(path))
}
/// resolution order:
/// 1. `./{path/to}.er`
/// 2. `./{path/to}/__init__.er`
/// 3. `std/{path/to}.er`
/// 4. `std/{path/to}/__init__.er`
pub fn resolve_real_path(&self, path: &Path) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local(path) {
Some(path)
} else if let Ok(path) = erg_std_path()
.join(format!("{}.er", path.display()))
.canonicalize()
{
Some(normalize_path(path))
} else if let Ok(path) = erg_std_path()
.join(format!("{}", path.display()))
.join("__init__.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
/// resolution order:
/// 1. `{path/to}.d.er`
/// 2. `{path/to}/__init__.d.er`
/// 3. `{path}/__pycache__/{to}.d.er`
/// 4. `{path/to}/__pycache__/__init__.d.er`
/// 5. `{path.d/to.d}/__init__.d.er`
/// 6. `{path.d/to.d}/__pycache__/__init__.d.er`
/// (and repeat for the project root)
/// 7. `std/{path/to}.d.er`
/// 8. `std/{path/to}/__init__.d.er`
/// 9. `site-packages/{path}/__pycache__/{to}.d.er`
/// 10. `site-packages/{path/to}/__pycache__/__init__.d.er`
pub fn resolve_decl_path(&self, path: &Path) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local_decl(self.dir(), path) {
return Some(path);
}
// e.g. root: lib/external/pandas.d, path: pandas/core/frame
if let Some(dir) = self.project_root().as_ref().and_then(|root| root.parent()) {
if let Ok(path) = self.resolve_local_decl(dir.to_path_buf(), path) {
return Some(path);
}
}
let py_roots = [erg_pystd_path, erg_py_external_lib_path];
for root in py_roots {
if let Some(path) = Self::resolve_std_decl_path(root(), path) {
return Some(path);
}
}
for site_packages in python_site_packages() {
if let Some(path) = Self::resolve_site_pkgs_decl_path(site_packages, path) {
return Some(path);
}
}
None
}
/// 1. `site-packages/{path/to}.d.er`
/// 2. `site-packages/{path.d/to.d}/__init__.d.er`
fn resolve_std_decl_path(root: PathBuf, path: &Path) -> Option<PathBuf> {
let mut path = add_postfix_foreach(path, ".d");
path.set_extension("d.er"); // set_extension overrides the previous one
if let Ok(path) = root.join(&path).canonicalize() {
Some(normalize_path(path))
// d.er -> .d
} else if let Ok(path) = root
.join({
path.set_extension("");
path
})
.join("__init__.d.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
/// 1. `site-packages/__pycache__/{path/to}.d.er`
/// 2. `site-packages/{path/to}/__pycache__/__init__.d.er`
///
/// e.g. `toml/encoder`
/// -> `site-packages/toml/__pycache__/encoder.d.er`, `site-packages/toml/encoder/__pycache__/__init__.d.er`
fn resolve_site_pkgs_decl_path(site_packages: PathBuf, path: &Path) -> Option<PathBuf> {
let dir = path.parent().unwrap_or_else(|| Path::new(""));
let mut file_path = PathBuf::from(path.file_stem().unwrap_or_default());
file_path.set_extension("d.er"); // set_extension overrides the previous one
if let Ok(path) = site_packages
.join(dir)
.join("__pycache__")
.join(&file_path)
.canonicalize()
{
Some(normalize_path(path))
} else if let Ok(path) = site_packages
.join(path)
.join("__pycache__")
.join("__init__.d.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
pub fn try_push_path(mut path: PathBuf, add: &Path) -> Result<PathBuf, String> {
path.pop(); // __init__.d.er
if let Ok(path) = path.join(add).canonicalize() {
Ok(normalize_path(path))
} else if let Ok(path) = path.join(format!("{}.d.er", add.display())).canonicalize() {
Ok(normalize_path(path))
} else if let Ok(path) = path
.join(format!("{}.d", add.display()))
.join("__init__.d.er")
.canonicalize()
{
Ok(normalize_path(path))
} else {
Err(format!("{} // {}", path.display(), add.display()))
}
}
pub fn decl_file_is(&self, decl_path: &Path) -> bool {
let mut py_path = self.unescaped_path().to_path_buf();
py_path.set_extension("d.er");
if decl_path == py_path {
return true;
}
let last = py_path.file_name().unwrap_or_default().to_os_string();
py_path.pop();
py_path.push("__pycache__");
py_path.push(last);
decl_path == py_path
}
}
#[derive(Debug, Clone)]
pub struct ErgConfig {
pub mode: ErgMode,
@ -759,7 +88,8 @@ pub struct ErgConfig {
pub quiet_repl: bool,
pub show_type: bool,
pub input: Input,
pub output_dir: Option<&'static str>,
pub output: Output,
pub dist_dir: Option<&'static str>,
/// module name to be executed
pub module: &'static str,
/// verbosity level for system messages.
@ -786,7 +116,8 @@ impl Default for ErgConfig {
quiet_repl: false,
show_type: false,
input: Input::repl(),
output_dir: None,
output: Output::stdout(),
dist_dir: None,
module: "<module>",
verbose: 1,
ps1: ">>> ",
@ -820,7 +151,7 @@ impl ErgConfig {
}
pub fn dump_path(&self) -> PathBuf {
if let Some(output) = &self.output_dir {
if let Some(output) = &self.dist_dir {
PathBuf::from(format!("{output}/{}", self.input.filename()))
} else {
self.input.full_path()
@ -828,7 +159,7 @@ impl ErgConfig {
}
pub fn dump_filename(&self) -> String {
if let Some(output) = &self.output_dir {
if let Some(output) = &self.dist_dir {
format!("{output}/{}", self.input.filename())
} else {
self.input.filename()
@ -948,7 +279,7 @@ impl ErgConfig {
.next()
.expect("the value of `--output-dir` is not passed")
.into_boxed_str();
cfg.output_dir = Some(Box::leak(output_dir));
cfg.dist_dir = Some(Box::leak(output_dir));
}
"--py-command" | "--python-command" => {
let py_command = args

View file

@ -5,7 +5,7 @@ use std::cmp::{self, Ordering};
use std::fmt;
use std::io::{stderr, BufWriter, Write as _};
use crate::config::{Input, InputKind};
use crate::io::{Input, InputKind};
use crate::style::Attribute;
use crate::style::Characters;
use crate::style::Color;
@ -985,12 +985,18 @@ macro_rules! impl_display_and_error {
}
pub trait MultiErrorDisplay<Item: ErrorDisplay>: Stream<Item> {
fn fmt_all_stderr(&self) {
fn write_all_stderr(&self) {
for err in self.iter() {
err.write_to_stderr();
}
}
fn write_all_to(&self, w: &mut impl std::io::Write) {
for err in self.iter() {
err.write_to(w);
}
}
fn fmt_all(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for err in self.iter() {
err.format(f)?;

739
crates/erg_common/io.rs Normal file
View file

@ -0,0 +1,739 @@
use std::ffi::OsStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Stdout, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::process::Stdio;
use crate::consts::{ERG_MODE, EXPERIMENTAL_MODE};
use crate::env::{erg_py_external_lib_path, erg_pystd_path, erg_std_path, python_site_packages};
use crate::pathutil::add_postfix_foreach;
use crate::python_util::get_sys_path;
use crate::random::random;
use crate::stdin::GLOBAL_STDIN;
use crate::{normalize_path, power_assert, read_file};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DummyStdin {
pub name: String,
current_line: usize,
lines: Vec<String>,
}
impl DummyStdin {
pub fn new(name: String, lines: Vec<String>) -> Self {
Self {
name,
current_line: 0,
lines,
}
}
pub fn read_line(&mut self) -> String {
let mut stdout = std::io::stdout();
if self.current_line >= self.lines.len() {
stdout.write_all("\n".as_bytes()).unwrap();
stdout.flush().unwrap();
// workaround: https://github.com/erg-lang/erg/issues/399
return "exit()".to_string();
}
let mut line = self.lines[self.current_line].clone();
self.current_line += 1;
line.push('\n');
stdout.write_all(line.as_bytes()).unwrap();
stdout.flush().unwrap();
line
}
pub fn reread_lines(&self, ln_begin: usize, ln_end: usize) -> Vec<String> {
self.lines[ln_begin - 1..=ln_end - 1].to_vec()
}
pub fn reread(&self) -> Option<String> {
self.lines.get(self.current_line).cloned()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputKind {
File(PathBuf),
REPL,
DummyREPL(DummyStdin),
/// same content as cfg.command
Pipe(String),
/// from command option | eval
Str(String),
Dummy,
}
impl InputKind {
pub const fn is_repl(&self) -> bool {
matches!(self, Self::REPL | Self::DummyREPL(_))
}
pub fn path(&self) -> Option<&Path> {
match self {
Self::File(path) => Some(path),
_ => None,
}
}
pub fn enclosed_name(&self) -> &str {
match self {
Self::File(filename) => filename.to_str().unwrap_or("_"),
Self::REPL | Self::DummyREPL(_) | Self::Pipe(_) => "<stdin>",
Self::Str(_) => "<string>",
Self::Dummy => "<dummy>",
}
}
pub fn dir(&self) -> PathBuf {
if let Self::File(path) = self {
let mut path = path.clone();
path.pop();
if path.parent().is_none() {
PathBuf::from(".")
} else {
path
}
} else {
PathBuf::from(".")
}
}
pub fn project_root(&self) -> Option<PathBuf> {
if let Self::File(path) = self {
let mut parent = path.clone();
while parent.pop() {
if parent.join("package.er").exists() {
return Some(parent);
}
}
None
} else {
None
}
}
}
/// Since input is not always only from files
/// Unify operations with `Input`
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Input {
pub(crate) kind: InputKind,
/// Unique id to avoid file name collision
id: u64,
}
impl From<PathBuf> for Input {
fn from(path: PathBuf) -> Self {
Self::file(path)
}
}
impl From<&Path> for Input {
fn from(path: &Path) -> Self {
Self::file(path.to_path_buf())
}
}
impl Input {
pub const fn new(kind: InputKind, id: u64) -> Self {
Self { kind, id }
}
pub fn file(path: PathBuf) -> Self {
Self::new(InputKind::File(path), random())
}
pub fn pipe(src: String) -> Self {
Self::new(InputKind::Pipe(src), random())
}
pub fn str(src: String) -> Self {
Self::new(InputKind::Str(src), random())
}
pub fn repl() -> Self {
Self::new(InputKind::REPL, random())
}
pub fn dummy() -> Self {
Self::new(InputKind::Dummy, random())
}
pub fn dummy_repl(stdin: DummyStdin) -> Self {
Self::new(InputKind::DummyREPL(stdin), random())
}
pub const fn is_repl(&self) -> bool {
self.kind.is_repl()
}
pub const fn id(&self) -> u64 {
self.id
}
pub fn path(&self) -> Option<&Path> {
self.kind.path()
}
pub fn dir(&self) -> PathBuf {
self.kind.dir()
}
pub fn project_root(&self) -> Option<PathBuf> {
self.kind.project_root()
}
pub fn enclosed_name(&self) -> &str {
self.kind.enclosed_name()
}
pub fn lineno(&self) -> usize {
GLOBAL_STDIN.lineno()
}
pub fn block_begin(&self) -> usize {
GLOBAL_STDIN.block_begin()
}
pub fn set_block_begin(&self) {
GLOBAL_STDIN.set_block_begin(self.lineno())
}
pub fn insert_whitespace(&self, whitespace: &str) {
GLOBAL_STDIN.insert_whitespace(whitespace);
}
pub fn set_indent(&self, indent: usize) {
GLOBAL_STDIN.set_indent(indent);
}
pub fn file_stem(&self) -> String {
match &self.kind {
InputKind::File(filename) => format!(
"{}_{}",
filename.file_stem().and_then(|f| f.to_str()).unwrap_or("_"),
self.id
),
InputKind::REPL | InputKind::Pipe(_) => format!("stdin_{}", self.id),
InputKind::DummyREPL(stdin) => format!("stdin_{}_{}", stdin.name, self.id),
InputKind::Str(_) => format!("string_{}", self.id),
InputKind::Dummy => "dummy".to_string(),
}
}
pub fn full_path(&self) -> PathBuf {
match &self.kind {
InputKind::File(filename) => {
PathBuf::from(format!("{}_{}", filename.display(), self.id))
}
_ => PathBuf::from(self.file_stem()),
}
}
pub fn filename(&self) -> String {
match &self.kind {
InputKind::File(filename) => format!(
"{}_{}",
filename.file_name().and_then(|f| f.to_str()).unwrap_or("_"),
self.id
),
_ => self.file_stem(),
}
}
pub fn unescaped_file_stem(&self) -> &str {
match &self.kind {
InputKind::File(filename) => {
filename.file_stem().and_then(|f| f.to_str()).unwrap_or("_")
}
InputKind::REPL | InputKind::Pipe(_) => "stdin",
InputKind::DummyREPL(_stdin) => "stdin",
InputKind::Str(_) => "string",
InputKind::Dummy => "dummy",
}
}
pub fn unescaped_filename(&self) -> &str {
match &self.kind {
InputKind::File(filename) => {
filename.file_name().and_then(|f| f.to_str()).unwrap_or("_")
}
InputKind::REPL | InputKind::Pipe(_) => "stdin",
InputKind::DummyREPL(_stdin) => "stdin",
InputKind::Str(_) => "string",
InputKind::Dummy => "dummy",
}
}
pub fn unescaped_path(&self) -> &Path {
match &self.kind {
InputKind::File(filename) => filename.as_path(),
InputKind::REPL | InputKind::Pipe(_) => Path::new("stdin"),
InputKind::DummyREPL(_stdin) => Path::new("stdin"),
InputKind::Str(_) => Path::new("string"),
InputKind::Dummy => Path::new("dummy"),
}
}
pub fn module_name(&self) -> String {
match &self.kind {
InputKind::File(filename) => {
let file_stem = if filename.file_stem() == Some(OsStr::new("__init__")) {
filename.parent().and_then(|p| p.file_stem())
} else {
filename.file_stem()
};
file_stem
.and_then(|f| f.to_str())
.unwrap_or("_")
.to_string()
}
InputKind::REPL | InputKind::Pipe(_) => "<stdin>".to_string(),
InputKind::DummyREPL(stdin) => stdin.name.clone(),
InputKind::Str(_) => "<string>".to_string(),
InputKind::Dummy => "<dummy>".to_string(),
}
}
pub fn read(&mut self) -> String {
match &mut self.kind {
InputKind::File(filename) => {
let file = match File::open(&filename) {
Ok(f) => f,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
let lossy = filename.to_str().unwrap().to_string();
println!("cannot open '{lossy}': [Errno {code}] {e}",);
process::exit(code);
}
};
match read_file(file) {
Ok(s) => s,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
println!(
"cannot read '{}': [Errno {code}] {e}",
filename.to_string_lossy()
);
process::exit(code);
}
}
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.read(),
InputKind::DummyREPL(dummy) => dummy.read_line(),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn try_read(&mut self) -> std::io::Result<String> {
match &mut self.kind {
InputKind::File(filename) => {
let file = File::open(filename)?;
read_file(file)
}
InputKind::Pipe(s) | InputKind::Str(s) => Ok(s.clone()),
InputKind::REPL => Ok(GLOBAL_STDIN.read()),
InputKind::DummyREPL(dummy) => Ok(dummy.read_line()),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn read_non_dummy(&self) -> String {
match &self.kind {
InputKind::File(filename) => {
let file = match File::open(filename) {
Ok(f) => f,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
let lossy = filename.to_str().unwrap().to_string();
println!("cannot open '{lossy}': [Errno {code}] {e}",);
process::exit(code);
}
};
match read_file(file) {
Ok(s) => s,
Err(e) => {
let code = e.raw_os_error().unwrap_or(1);
println!(
"cannot read '{}': [Errno {code}] {e}",
filename.to_string_lossy()
);
process::exit(code);
}
}
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.read(),
InputKind::Dummy | InputKind::DummyREPL(_) => panic!("cannot read from a dummy file"),
}
}
pub fn reread_lines(&self, ln_begin: usize, ln_end: usize) -> Vec<String> {
power_assert!(ln_begin, >=, 1);
match &self.kind {
InputKind::File(filename) => match File::open(filename) {
Ok(file) => {
let mut codes = vec![];
let mut lines = BufReader::new(file).lines().skip(ln_begin - 1);
for _ in ln_begin..=ln_end {
codes.push(lines.next().unwrap_or_else(|| Ok("".to_string())).unwrap());
}
codes
}
Err(_) => vec!["<file not found>".into()],
},
InputKind::Pipe(s) | InputKind::Str(s) => s.split('\n').collect::<Vec<_>>()
[ln_begin - 1..=ln_end - 1]
.iter()
.map(|s| s.to_string())
.collect(),
InputKind::REPL => {
if ln_begin == ln_end {
vec![GLOBAL_STDIN.reread()]
} else {
GLOBAL_STDIN.reread_lines(ln_begin, ln_end)
}
}
InputKind::DummyREPL(dummy) => dummy.reread_lines(ln_begin, ln_end),
InputKind::Dummy => panic!("cannot read lines from a dummy file"),
}
}
pub fn reread(&self) -> String {
match &self.kind {
InputKind::File(path) => {
let mut reader = BufReader::new(File::open(path).unwrap());
let mut buf = String::new();
reader.read_to_string(&mut buf).unwrap();
buf
}
InputKind::Pipe(s) | InputKind::Str(s) => s.clone(),
InputKind::REPL => GLOBAL_STDIN.reread().trim_end().to_owned(),
InputKind::DummyREPL(dummy) => dummy.reread().unwrap_or_default(),
InputKind::Dummy => panic!("cannot read from a dummy file"),
}
}
pub fn sys_path(&self) -> Result<Vec<PathBuf>, std::io::Error> {
get_sys_path(self.unescaped_path().parent())
}
/// resolution order:
/// 1. `{path/to}.er`
/// 2. `{path/to}/__init__.er`
fn resolve_local(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
let mut dir = self.dir();
dir.push(path);
dir.set_extension("er"); // {path/to}.er
let path = dir.canonicalize().or_else(|_| {
dir.pop(); // {path}
dir.push(path.iter().last().unwrap_or_default()); // {path/to}
dir.push("__init__.er"); // -> {path/to}/__init__.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
fn resolve_local_decl(&self, dir: PathBuf, path: &Path) -> Result<PathBuf, std::io::Error> {
self._resolve_local_decl(dir.clone(), path).or_else(|_| {
let path = add_postfix_foreach(path, ".d");
self._resolve_local_decl(dir, &path)
})
}
/// resolution order:
/// 1. `{path/to}.d.er`
/// 2. `{path/to}/__init__.d.er`
/// 3. `{path}/__pycache__/{to}.d.er`
/// 4. `{path/to}/__pycache__/__init__.d.er`
fn _resolve_local_decl(
&self,
mut dir: PathBuf,
path: &Path,
) -> Result<PathBuf, std::io::Error> {
let mut comps = path.components();
let last = comps
.next_back()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "path is empty"))?;
let last_path = Path::new(&last);
dir.push(comps);
dir.push(last_path);
dir.set_extension("d.er"); // {path/to}.d.er
let path = dir
.canonicalize()
.or_else(|_| {
dir.pop(); // {path/to}.d.er -> {path}
dir.push(last_path); // -> {path/to}
dir.push("__init__.d.er"); // -> {path/to}/__init__.d.er
dir.canonicalize()
})
.or_else(|_| {
dir.pop(); // -> {path/to}
dir.pop(); // -> {path}
dir.push("__pycache__"); // -> {path}/__pycache__
dir.push(last_path); // -> {path}/__pycache__/{to}
dir.set_extension("d.er"); // -> {path}/__pycache__/{to}.d.er
dir.canonicalize()
})
.or_else(|_| {
dir.pop(); // -> {path}/__pycache__
dir.pop(); // -> {path}
dir.push(last_path); // -> {path/to}
dir.push("__pycache__"); // -> {path/to}/__pycache__
dir.push("__init__.d.er"); // -> {path/to}/__pycache__/__init__.d.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
fn resolve_local_py(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
let mut dir = self.dir();
dir.push(path);
dir.set_extension("py");
let path = dir.canonicalize().or_else(|_| {
dir.pop();
dir.push(path);
dir.push("__init__.py"); // {path}/__init__.er
dir.canonicalize()
})?;
Ok(normalize_path(path))
}
pub fn resolve_py(&self, path: &Path) -> Result<PathBuf, std::io::Error> {
if ERG_MODE || path.starts_with("./") {
if let Ok(path) = self.resolve_local_py(path) {
return Ok(path);
}
}
for sys_path in self.sys_path()? {
let mut dir = sys_path;
dir.push(path);
dir.set_extension("py");
if dir.exists() {
return Ok(normalize_path(dir));
}
dir.pop();
dir.push(path);
dir.push("__init__.py");
if dir.exists() {
return Ok(normalize_path(dir));
}
if !EXPERIMENTAL_MODE {
break;
}
}
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("cannot find module `{}`", path.display()),
))
}
pub fn resolve_path(&self, path: &Path) -> Option<PathBuf> {
self.resolve_real_path(path)
.or_else(|| self.resolve_decl_path(path))
}
/// resolution order:
/// 1. `./{path/to}.er`
/// 2. `./{path/to}/__init__.er`
/// 3. `std/{path/to}.er`
/// 4. `std/{path/to}/__init__.er`
pub fn resolve_real_path(&self, path: &Path) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local(path) {
Some(path)
} else if let Ok(path) = erg_std_path()
.join(format!("{}.er", path.display()))
.canonicalize()
{
Some(normalize_path(path))
} else if let Ok(path) = erg_std_path()
.join(format!("{}", path.display()))
.join("__init__.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
/// resolution order:
/// 1. `{path/to}.d.er`
/// 2. `{path/to}/__init__.d.er`
/// 3. `{path}/__pycache__/{to}.d.er`
/// 4. `{path/to}/__pycache__/__init__.d.er`
/// 5. `{path.d/to.d}/__init__.d.er`
/// 6. `{path.d/to.d}/__pycache__/__init__.d.er`
/// (and repeat for the project root)
/// 7. `std/{path/to}.d.er`
/// 8. `std/{path/to}/__init__.d.er`
/// 9. `site-packages/{path}/__pycache__/{to}.d.er`
/// 10. `site-packages/{path/to}/__pycache__/__init__.d.er`
pub fn resolve_decl_path(&self, path: &Path) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local_decl(self.dir(), path) {
return Some(path);
}
// e.g. root: lib/external/pandas.d, path: pandas/core/frame
if let Some(mut dir) = self.project_root() {
let mut path = path.iter().skip(1).collect::<PathBuf>();
if path == Path::new("") {
path.extend(dir.iter().last());
dir.pop();
}
if let Ok(path) = self.resolve_local_decl(dir, &path) {
return Some(path);
}
}
let py_roots = [erg_pystd_path, erg_py_external_lib_path];
for root in py_roots {
if let Some(path) = Self::resolve_std_decl_path(root(), path) {
return Some(path);
}
}
for site_packages in python_site_packages() {
if let Some(path) = Self::resolve_site_pkgs_decl_path(site_packages, path) {
return Some(path);
}
}
None
}
/// 1. `site-packages/{path/to}.d.er`
/// 2. `site-packages/{path.d/to.d}/__init__.d.er`
fn resolve_std_decl_path(root: PathBuf, path: &Path) -> Option<PathBuf> {
let mut path = add_postfix_foreach(path, ".d");
path.set_extension("d.er"); // set_extension overrides the previous one
if let Ok(path) = root.join(&path).canonicalize() {
Some(normalize_path(path))
// d.er -> .d
} else if let Ok(path) = root
.join({
path.set_extension("");
path
})
.join("__init__.d.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
/// 1. `site-packages/__pycache__/{path/to}.d.er`
/// 2. `site-packages/{path/to}/__pycache__/__init__.d.er`
///
/// e.g. `toml/encoder`
/// -> `site-packages/toml/__pycache__/encoder.d.er`, `site-packages/toml/encoder/__pycache__/__init__.d.er`
fn resolve_site_pkgs_decl_path(site_packages: PathBuf, path: &Path) -> Option<PathBuf> {
let dir = path.parent().unwrap_or_else(|| Path::new(""));
let mut file_path = PathBuf::from(path.file_stem().unwrap_or_default());
file_path.set_extension("d.er"); // set_extension overrides the previous one
if let Ok(path) = site_packages
.join(dir)
.join("__pycache__")
.join(&file_path)
.canonicalize()
{
Some(normalize_path(path))
} else if let Ok(path) = site_packages
.join(path)
.join("__pycache__")
.join("__init__.d.er")
.canonicalize()
{
Some(normalize_path(path))
} else {
None
}
}
pub fn try_push_path(mut path: PathBuf, add: &Path) -> Result<PathBuf, String> {
path.pop(); // __init__.d.er
if let Ok(path) = path.join(add).canonicalize() {
Ok(normalize_path(path))
} else if let Ok(path) = path.join(format!("{}.d.er", add.display())).canonicalize() {
Ok(normalize_path(path))
} else if let Ok(path) = path
.join(format!("{}.d", add.display()))
.join("__init__.d.er")
.canonicalize()
{
Ok(normalize_path(path))
} else {
Err(format!("{} // {}", path.display(), add.display()))
}
}
pub fn decl_file_is(&self, decl_path: &Path) -> bool {
let mut py_path = self.unescaped_path().to_path_buf();
py_path.set_extension("d.er");
if decl_path == py_path {
return true;
}
let last = py_path.file_name().unwrap_or_default().to_os_string();
py_path.pop();
py_path.push("__pycache__");
py_path.push(last);
decl_path == py_path
}
}
#[derive(Debug)]
pub enum Output {
Stdout(Stdout),
File(File, String),
Null,
}
impl Clone for Output {
fn clone(&self) -> Self {
match self {
Self::Null => Self::Null,
Self::Stdout(_) => Self::stdout(),
Self::File(_, filename) => Self::file(filename.clone()),
}
}
}
impl std::io::Write for Output {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Output::Stdout(stdout) => stdout.write(buf),
Output::File(file, _) => file.write(buf),
Output::Null => Ok(buf.len()),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Output::Stdout(stdout) => stdout.flush(),
Output::File(file, _) => file.flush(),
Output::Null => Ok(()),
}
}
}
impl From<Output> for Stdio {
fn from(output: Output) -> Self {
match output {
Output::Stdout(_stdout) => Stdio::inherit(),
Output::File(file, _) => Stdio::from(file),
Output::Null => Stdio::null(),
}
}
}
impl Output {
pub fn stdout() -> Self {
Self::Stdout(std::io::stdout())
}
pub fn file(filename: String) -> Self {
Self::File(File::open(&filename).unwrap(), filename)
}
}

View file

@ -13,6 +13,7 @@ pub mod error;
pub mod fresh;
pub mod fxhash;
pub mod help_messages;
pub mod io;
pub mod lang;
pub mod levenshtein;
pub mod macros;

View file

@ -3,7 +3,7 @@
//! CPythonを呼び出すためのユーティリティー
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::process::{Command, Stdio};
use crate::fn_name_full;
use crate::pathutil::remove_verbatim;
@ -727,10 +727,11 @@ pub fn get_sys_path(working_dir: Option<&Path>) -> Result<Vec<PathBuf>, std::io:
}
/// executes over a shell, cause `python` may not exist as an executable file (like pyenv)
pub fn exec_pyc<S: Into<String>>(
pub fn exec_pyc<S: Into<String>, T: Into<Stdio>>(
file: S,
py_command: Option<&str>,
argv: &[&'static str],
stdout: T,
) -> Option<i32> {
let command = py_command
.map(ToString::to_string)
@ -741,6 +742,7 @@ pub fn exec_pyc<S: Into<String>>(
.arg(command)
.arg(&file.into())
.args(argv)
.stdout(stdout)
.spawn()
.expect("cannot execute python")
} else {
@ -748,6 +750,7 @@ pub fn exec_pyc<S: Into<String>>(
Command::new("sh")
.arg("-c")
.arg(exec_command)
.stdout(stdout)
.spawn()
.expect("cannot execute python")
};

View file

@ -6,7 +6,7 @@ use std::io::{stdin, BufRead, BufReader};
#[cfg(feature = "full-repl")]
use crossterm::{
cursor::{CursorShape, MoveToColumn, SetCursorShape},
cursor::MoveToColumn,
event::{read, Event, KeyCode, KeyEvent, KeyModifiers},
execute,
style::Print,
@ -103,7 +103,6 @@ impl StdinReader {
pub fn read(&mut self) -> String {
enable_raw_mode().unwrap();
let mut output = std::io::stdout();
execute!(output, SetCursorShape(CursorShape::Line)).unwrap();
let mut line = String::new();
self.input(&mut line).unwrap();
disable_raw_mode().unwrap();
@ -124,7 +123,8 @@ impl StdinReader {
{
consult_history = false;
match (code, modifiers) {
(KeyCode::Char('z'), KeyModifiers::CONTROL) => {
(KeyCode::Char('z'), KeyModifiers::CONTROL)
| (KeyCode::Char('d'), KeyModifiers::CONTROL) => {
println!();
line.clear();
line.push_str(":exit");

View file

@ -9,9 +9,10 @@ use std::mem;
use std::process;
use std::slice::{Iter, IterMut};
use crate::config::{ErgConfig, Input, InputKind};
use crate::config::ErgConfig;
use crate::consts::{BUILD_DATE, GIT_HASH_SHORT, SEMVER};
use crate::error::{ErrorDisplay, ErrorKind, Location, MultiErrorDisplay};
use crate::io::{Input, InputKind};
use crate::{addr_eq, chomp, log, switch_unreachable};
pub trait DequeStream<T>: Sized {
@ -249,6 +250,10 @@ pub trait Stream<T>: Sized {
{
self.ref_mut_payload().extend(iter);
}
fn split_off(&mut self, at: usize) -> Vec<T> {
self.ref_mut_payload().split_off(at)
}
}
#[macro_export]
@ -792,7 +797,7 @@ pub trait Runnable: Sized + Default {
instance.quit_successfully(output);
}
num_errors += errs.len();
errs.fmt_all_stderr();
errs.write_all_stderr();
}
}
instance.input().set_block_begin();
@ -916,7 +921,7 @@ pub trait Runnable: Sized + Default {
return ExitStatus::new(0, 0, num_errors);
}
num_errors += errs.len();
errs.fmt_all_stderr();
errs.write_all_stderr();
}
}
instance.input().set_block_begin();
@ -931,7 +936,7 @@ pub trait Runnable: Sized + Default {
Ok(status) => status,
Err(errs) => {
num_errors += errs.len();
errs.fmt_all_stderr();
errs.write_all_stderr();
ExitStatus::new(1, 0, num_errors)
}
}

View file

@ -10,7 +10,7 @@ use erg_parser::build_ast::ASTBuilder;
use crate::artifact::{BuildRunnable, Buildable, CompleteArtifact, IncompleteArtifact};
use crate::context::{Context, ContextProvider, ModuleContext};
use crate::effectcheck::SideEffectChecker;
use crate::error::{CompileError, CompileErrors};
use crate::error::{CompileError, CompileErrors, LowerWarnings};
use crate::lower::ASTLowerer;
use crate::module::SharedCompilerResource;
use crate::ownercheck::OwnershipChecker;
@ -66,18 +66,26 @@ impl Runnable for HIRBuilder {
fn exec(&mut self) -> Result<ExitStatus, Self::Errs> {
let mut builder = ASTBuilder::new(self.cfg().copy());
let ast = builder.build(self.cfg_mut().input.read())?;
let artifact = self.check(ast, "exec").map_err(|arti| arti.errors)?;
artifact.warns.fmt_all_stderr();
let artifact = builder
.build(self.cfg_mut().input.read())
.map_err(|arti| arti.errors)?;
artifact.warns.write_all_stderr();
let artifact = self
.check(artifact.ast, "exec")
.map_err(|arti| arti.errors)?;
artifact.warns.write_all_stderr();
println!("{}", artifact.object);
Ok(ExitStatus::compile_passed(artifact.warns.len()))
}
fn eval(&mut self, src: String) -> Result<String, Self::Errs> {
let mut builder = ASTBuilder::new(self.cfg().copy());
let ast = builder.build(src)?;
let artifact = self.check(ast, "eval").map_err(|arti| arti.errors)?;
artifact.warns.fmt_all_stderr();
let artifact = builder.build(src).map_err(|arti| arti.errors)?;
artifact.warns.write_all_stderr();
let artifact = self
.check(artifact.ast, "eval")
.map_err(|arti| arti.errors)?;
artifact.warns.write_all_stderr();
Ok(artifact.object.to_string())
}
}
@ -148,10 +156,13 @@ impl HIRBuilder {
mode: &str,
) -> Result<CompleteArtifact, IncompleteArtifact> {
let mut ast_builder = ASTBuilder::new(self.cfg().copy());
let ast = ast_builder.build(src).map_err(|errs| {
IncompleteArtifact::new(None, CompileErrors::from(errs), CompileErrors::empty())
})?;
self.check(ast, mode)
let artifact = ast_builder
.build(src)
.map_err(|iart| IncompleteArtifact::new(None, iart.errors.into(), iart.warns.into()))?;
self.lowerer
.warns
.extend(LowerWarnings::from(artifact.warns));
self.check(artifact.ast, mode)
}
pub fn pop_mod_ctx(&mut self) -> Option<ModuleContext> {

View file

@ -7,9 +7,10 @@ use std::process;
use crate::ty::codeobj::{CodeObj, CodeObjFlags, MakeFunctionFlags};
use crate::ty::value::GenTypeObj;
use erg_common::cache::CacheSet;
use erg_common::config::{ErgConfig, Input};
use erg_common::config::ErgConfig;
use erg_common::env::erg_std_path;
use erg_common::error::{ErrorDisplay, Location};
use erg_common::io::Input;
use erg_common::opcode::{CommonOpcode, CompareOp};
use erg_common::opcode308::Opcode308;
use erg_common::opcode310::Opcode310;
@ -646,8 +647,8 @@ impl PyCodeGenerator {
}
StoreLoadKind::Local | StoreLoadKind::LocalConst => match acc_kind {
Name => LOAD_NAME as u8,
Attr => LOAD_ATTR as u8,
Method => LOAD_METHOD as u8,
UnboundAttr => LOAD_ATTR as u8,
BoundAttr => LOAD_METHOD as u8,
},
}
}
@ -669,9 +670,9 @@ impl PyCodeGenerator {
StoreLoadKind::Local | StoreLoadKind::LocalConst => {
match acc_kind {
Name => STORE_NAME as u8,
Attr => STORE_ATTR as u8,
UnboundAttr => STORE_ATTR as u8,
// cannot overwrite methods directly
Method => STORE_ATTR as u8,
BoundAttr => STORE_ATTR as u8,
}
}
}
@ -792,9 +793,9 @@ impl PyCodeGenerator {
log!(info "entered {} ({ident})", fn_name!());
let escaped = escape_ident(ident);
let name = self
.local_search(&escaped, Attr)
.local_search(&escaped, UnboundAttr)
.unwrap_or_else(|| self.register_attr(escaped));
let instr = self.select_load_instr(name.kind, Attr);
let instr = self.select_load_instr(name.kind, UnboundAttr);
self.write_instr(instr);
self.write_arg(name.idx);
if self.py_version.minor >= Some(11) {
@ -809,9 +810,9 @@ impl PyCodeGenerator {
}
let escaped = escape_ident(ident);
let name = self
.local_search(&escaped, Method)
.local_search(&escaped, BoundAttr)
.unwrap_or_else(|| self.register_method(escaped));
let instr = self.select_load_instr(name.kind, Method);
let instr = self.select_load_instr(name.kind, BoundAttr);
self.write_instr(instr);
self.write_arg(name.idx);
if self.py_version.minor >= Some(11) {
@ -866,7 +867,7 @@ impl PyCodeGenerator {
}
Accessor::Attr(attr) => {
self.emit_expr(*attr.obj);
self.emit_store_instr(attr.ident, Attr);
self.emit_store_instr(attr.ident, UnboundAttr);
}
}
}
@ -1010,7 +1011,7 @@ impl PyCodeGenerator {
self.emit_precall_and_call(argc);
} else {
match kind {
AccessKind::Method => self.write_instr(Opcode310::CALL_METHOD),
AccessKind::BoundAttr => self.write_instr(Opcode310::CALL_METHOD),
_ => self.write_instr(Opcode310::CALL_FUNCTION),
}
self.write_arg(argc);
@ -2188,7 +2189,7 @@ impl PyCodeGenerator {
let is_py_api = method_name.is_py_api();
self.emit_expr(obj);
self.emit_load_method_instr(method_name);
self.emit_args_311(args, Method, is_py_api);
self.emit_args_311(args, BoundAttr, is_py_api);
}
fn emit_var_args_311(&mut self, pos_len: usize, var_args: &PosArg) {
@ -3121,7 +3122,7 @@ impl PyCodeGenerator {
self.emit_load_name_instr(Identifier::private("#path"));
self.emit_load_method_instr(Identifier::public("append"));
self.emit_load_const(erg_std_path().to_str().unwrap());
self.emit_call_instr(1, Method);
self.emit_call_instr(1, BoundAttr);
self.stack_dec();
self.emit_pop_top();
let erg_std_mod = Identifier::public("_erg_std_prelude");

View file

@ -80,19 +80,31 @@ impl Name {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessKind {
Name,
Attr,
Method,
/// class/module attr
/// e.g. `Str.center`
UnboundAttr,
/// method/instance attr
/// e.g. `"aaa".center`
///
/// can also access class/module attrs
BoundAttr,
}
impl AccessKind {
pub const fn is_local(&self) -> bool {
matches!(self, Self::Name)
}
pub const fn is_attr(&self) -> bool {
matches!(self, Self::Attr)
pub const fn is_unbound_attr(&self) -> bool {
matches!(self, Self::UnboundAttr)
}
pub const fn is_method(&self) -> bool {
matches!(self, Self::Method)
pub const fn is_bound_attr(&self) -> bool {
matches!(self, Self::BoundAttr)
}
pub fn matches(&self, vi: &VarInfo) -> bool {
match self {
Self::Name | Self::BoundAttr => true,
Self::UnboundAttr => !vi.kind.is_instance_attr(),
}
}
}
@ -155,19 +167,19 @@ impl Runnable for Compiler {
let warns = self
.compile_and_dump_as_pyc(path, src, "exec")
.map_err(|eart| {
eart.warns.fmt_all_stderr();
eart.warns.write_all_stderr();
eart.errors
})?;
warns.fmt_all_stderr();
warns.write_all_stderr();
Ok(ExitStatus::compile_passed(warns.len()))
}
fn eval(&mut self, src: String) -> Result<String, CompileErrors> {
let arti = self.compile(src, "eval").map_err(|eart| {
eart.warns.fmt_all_stderr();
eart.warns.write_all_stderr();
eart.errors
})?;
arti.warns.fmt_all_stderr();
arti.warns.write_all_stderr();
Ok(arti.object.code_info(Some(self.code_generator.py_version)))
}
}

View file

@ -301,6 +301,13 @@ impl Context {
panic!("err: {err}");
}
}
} else if typ.has_undoable_linked_var() {
if let Err(err) = self.overwrite_typarams(typ, rhs) {
Self::undo_substitute_typarams(typ);
if DEBUG_MODE {
panic!("err: {err}");
}
}
}
for rhs_sup in rhs_ctx.super_traits.iter() {
// Not `supertype_of` (only structures are compared)
@ -459,8 +466,8 @@ impl Context {
// => ?P.undoable_link(Int)
// => Mul Int :> Int
(FreeVar(lfv), rhs) => {
if let FreeKind::Linked(t) | FreeKind::UndoableLinked { t, .. } = &*lfv.borrow() {
return self.supertype_of(t, rhs);
if let Some(t) = lfv.get_linked() {
return self.supertype_of(&t, rhs);
}
if let Some((_sub, sup)) = lfv.get_subsup() {
lfv.undoable_link(rhs);
@ -482,8 +489,8 @@ impl Context {
}
}
(lhs, FreeVar(rfv)) => {
if let FreeKind::Linked(t) | FreeKind::UndoableLinked { t, .. } = &*rfv.borrow() {
return self.supertype_of(lhs, t);
if let Some(t) = rfv.get_linked() {
return self.supertype_of(lhs, &t);
}
if let Some((sub, _sup)) = rfv.get_subsup() {
rfv.undoable_link(lhs);
@ -908,15 +915,23 @@ impl Context {
}
}
_ => {
if let (Ok(sup), Ok(sub)) = (
match (
self.convert_tp_into_type(sup_p.clone()),
self.convert_tp_into_type(sub_p.clone()),
) {
return match variance {
Variance::Contravariant => self.subtype_of(&sup, &sub),
Variance::Covariant => self.supertype_of(&sup, &sub),
Variance::Invariant => self.same_type_of(&sup, &sub),
};
(Ok(sup), Ok(sub)) => {
return match variance {
Variance::Contravariant => self.subtype_of(&sup, &sub),
Variance::Covariant => self.supertype_of(&sup, &sub),
Variance::Invariant => self.same_type_of(&sup, &sub),
};
}
(Err(le), Err(re)) => {
log!(err "cannot convert {le}, {re} to types")
}
(Err(err), _) | (_, Err(err)) => {
log!(err "cannot convert {err} to a type");
}
}
self.eq_tp(sup_p, sub_p)
}

View file

@ -1338,6 +1338,11 @@ impl Context {
TyParam::FreeVar(fv) if fv.is_linked() => self.convert_tp_into_type(fv.crack().clone()),
TyParam::Type(t) => Ok(t.as_ref().clone()),
TyParam::Mono(name) => Ok(Type::Mono(name)),
TyParam::App { name, args } => Ok(Type::Poly { name, params: args }),
TyParam::Proj { obj, attr } => {
let lhs = self.convert_tp_into_type(*obj)?;
Ok(lhs.proj(attr))
}
// TyParam::Erased(_t) => Ok(Type::Obj),
TyParam::Value(v) => self.convert_value_into_type(v).map_err(TyParam::Value),
// TODO: Dict, Set
@ -1650,6 +1655,20 @@ impl Context {
Ok(())
}
pub(crate) fn overwrite_typarams(&self, qt: &Type, st: &Type) -> EvalResult<()> {
let qtps = qt.typarams();
let stps = st.typarams();
if qt.qual_name() != st.qual_name() || qtps.len() != stps.len() {
log!(err "{qt} / {st}");
log!(err "[{}] [{}]", erg_common::fmt_vec(&qtps), erg_common::fmt_vec(&stps));
return Ok(()); // TODO: e.g. Sub(Int) / Eq and Sub(?T)
}
for (qtp, stp) in qtps.into_iter().zip(stps.into_iter()) {
self.overwrite_typaram(qtp, stp)?;
}
Ok(())
}
fn substitute_typaram(&self, qtp: TyParam, stp: TyParam) -> EvalResult<()> {
match qtp {
TyParam::FreeVar(ref fv) if fv.is_generalized() => {
@ -1693,6 +1712,49 @@ impl Context {
Ok(())
}
fn overwrite_typaram(&self, qtp: TyParam, stp: TyParam) -> EvalResult<()> {
match qtp {
TyParam::FreeVar(ref fv) if fv.is_undoable_linked() => {
if !stp.is_unbound_var() || !stp.is_generalized() {
fv.undoable_link(&stp);
}
if let Err(errs) = self.sub_unify_tp(&stp, &qtp, None, &(), false) {
log!(err "{errs}");
}
Ok(())
}
TyParam::Type(qt) => self.overwrite_type(stp, *qt),
TyParam::Value(ValueObj::Type(qt)) => self.overwrite_type(stp, qt.into_typ()),
_ => Ok(()),
}
}
fn overwrite_type(&self, stp: TyParam, qt: Type) -> EvalResult<()> {
let st = self.convert_tp_into_type(stp).map_err(|tp| {
EvalError::not_a_type_error(
self.cfg.input.clone(),
line!() as usize,
().loc(),
self.caused_by(),
&tp.to_string(),
)
})?;
if qt.has_undoable_linked_var() {
if let Ok(qt) = <&FreeTyVar>::try_from(&qt) {
if !st.is_unbound_var() || !st.is_generalized() {
qt.undoable_link(&st);
}
}
}
if !st.is_unbound_var() || !st.is_generalized() {
self.overwrite_typarams(&qt, &st)?;
}
if let Err(errs) = self.sub_unify(&st, &qt, &(), None) {
log!(err "{errs}");
}
Ok(())
}
pub(crate) fn undo_substitute_typarams(substituted_q: &Type) {
for tp in substituted_q.typarams().into_iter() {
match tp {

View file

@ -303,7 +303,9 @@ pub(crate) fn sub_tpdict_get<'d>(
let mut matches = vec![];
for (k, v) in dict.iter() {
match (<&Type>::try_from(key), <&Type>::try_from(k)) {
(Ok(idx), Ok(kt)) if ctx.subtype_of(&idx.lower_bounded(), &kt.lower_bounded()) => {
(Ok(idx), Ok(kt))
if ctx.subtype_of(&idx.lower_bounded(), &kt.lower_bounded()) || dict.len() == 1 =>
{
matches.push((idx, kt, v));
}
(_, _) if key == k => {

View file

@ -2,9 +2,9 @@
use std::option::Option; // conflicting to Type::Option
use std::path::{Path, PathBuf};
use erg_common::config::Input;
use erg_common::consts::{ERG_MODE, PYTHON_MODE};
use erg_common::error::{ErrorCore, Location, SubMessage};
use erg_common::io::Input;
use erg_common::levenshtein;
use erg_common::set::Set;
use erg_common::traits::{Locational, NoTypeDisplay, Stream};
@ -420,7 +420,7 @@ impl Context {
) -> Triple<VarInfo, TyCheckError> {
if let Some(vi) = self.get_current_scope_var(&ident.name) {
match self.validate_visibility(ident, vi, input, namespace) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
return Triple::Ok(vi.clone());
}
Err(err) => {
@ -428,6 +428,7 @@ impl Context {
return Triple::Err(err);
}
}
_ => {}
}
} else if let Some((name, _vi)) = self
.future_defined_locals
@ -453,6 +454,17 @@ impl Context {
self.get_similar_name(ident.inspect()),
));
}
for (_, method_ctx) in self.methods_list.iter() {
match method_ctx.rec_get_var_info(ident, acc_kind, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
Triple::Err(e) => {
return Triple::Err(e);
}
Triple::None => {}
}
}
if acc_kind.is_local() {
if let Some(parent) = self.get_outer().or_else(|| self.get_builtins()) {
return parent.rec_get_var_info(ident, acc_kind, input, namespace);
@ -468,7 +480,7 @@ impl Context {
) -> Option<&mut VarInfo> {
if let Some(vi) = self.get_current_scope_var(&ident.name) {
match self.validate_visibility(ident, vi, &self.cfg.input, self) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
let vi = self.get_mut_current_scope_var(&ident.name).unwrap();
return Some(vi);
}
@ -477,6 +489,7 @@ impl Context {
return None;
}
}
_ => {}
}
}
if acc_kind.is_local() {
@ -500,7 +513,7 @@ impl Context {
.or_else(|| self.future_defined_locals.get(&ident.inspect()[..]))
{
match self.validate_visibility(ident, vi, input, namespace) {
Ok(()) => {
Ok(()) if acc_kind.matches(vi) => {
return Triple::Ok(vi.clone());
}
Err(err) => {
@ -508,6 +521,7 @@ impl Context {
return Triple::Err(err);
}
}
_ => {}
}
}
if acc_kind.is_local() {
@ -562,9 +576,10 @@ impl Context {
}
_ => {}
}
// class/module attr
if let Ok(singular_ctxs) = self.get_singular_ctxs_by_hir_expr(obj, namespace) {
for ctx in singular_ctxs {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::UnboundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -575,7 +590,8 @@ impl Context {
}
}
}
match self.get_attr_from_nominal_t(obj, ident, input, namespace) {
// bound method/instance attr
match self.get_bound_attr_from_nominal_t(obj, ident, input, namespace) {
Triple::Ok(vi) => {
if let Some(self_t) = vi.t.self_t() {
match self
@ -636,7 +652,7 @@ impl Context {
Triple::None
}
fn get_attr_from_nominal_t(
fn get_bound_attr_from_nominal_t(
&self,
obj: &hir::Expr,
ident: &Identifier,
@ -646,7 +662,7 @@ impl Context {
let self_t = obj.t();
if let Some(sups) = self.get_nominal_super_type_ctxs(&self_t) {
for ctx in sups {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -657,7 +673,7 @@ impl Context {
}
// if self is a methods context
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(ident, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -691,7 +707,7 @@ impl Context {
}
};
for ctx in ctxs {
match ctx.rec_get_var_info(ident, AccessKind::Attr, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
obj.ref_t().coerce();
return Triple::Ok(vi);
@ -702,7 +718,7 @@ impl Context {
_ => {}
}
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(ident, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(ident, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(vi) => {
return Triple::Ok(vi);
}
@ -946,7 +962,7 @@ impl Context {
}
}
if let Some(ctx) = self.get_same_name_context(&ctx.name) {
match ctx.rec_get_var_info(attr_name, AccessKind::Method, input, namespace) {
match ctx.rec_get_var_info(attr_name, AccessKind::BoundAttr, input, namespace) {
Triple::Ok(t) => {
return Ok(t);
}
@ -3072,4 +3088,58 @@ impl Context {
)))
}
}
pub(crate) fn get_instance_attr(&self, name: &str) -> Option<&VarInfo> {
if let Some(vi) = self.locals.get(name) {
if vi.kind.is_instance_attr() {
return Some(vi);
}
}
if let Some(vi) = self.decls.get(name) {
if vi.kind.is_instance_attr() {
return Some(vi);
}
}
if self.kind.is_method_def() {
self.get_nominal_type_ctx(&mono(&self.name))
.and_then(|(_, ctx)| ctx.get_instance_attr(name))
} else {
self.methods_list.iter().find_map(|(_, ctx)| {
if ctx.kind.is_trait_impl() {
None
} else {
ctx.get_instance_attr(name)
}
})
}
}
/// does not remove instance attribute declarations
pub(crate) fn remove_class_attr(&mut self, name: &str) -> Option<(VarName, VarInfo)> {
if let Some((k, v)) = self.locals.remove_entry(name) {
if v.kind.is_instance_attr() {
self.locals.insert(k, v);
} else {
return Some((k, v));
}
} else if let Some((k, v)) = self.decls.remove_entry(name) {
if v.kind.is_instance_attr() {
self.decls.insert(k, v);
} else {
return Some((k, v));
}
}
if self.kind.is_method_def() {
self.get_mut_nominal_type_ctx(&mono(&self.name))
.and_then(|(_, ctx)| ctx.remove_class_attr(name))
} else {
self.methods_list.iter_mut().find_map(|(_, ctx)| {
if ctx.kind.is_trait_impl() {
None
} else {
ctx.remove_class_attr(name)
}
})
}
}
}

View file

@ -574,19 +574,23 @@ impl Context {
) -> TyCheckResult<Type> {
match poly_spec.acc.to_string().trim_start_matches([':', '.']) {
"Array" => {
let ctx = self
.get_nominal_type_ctx(&array_t(Type::Obj, TyParam::Failure))
.unwrap()
.1;
// TODO: kw
let mut args = poly_spec.args.pos_args();
if let Some(first) = args.next() {
let t = self.instantiate_const_expr_as_type(
&first.expr,
None,
Some((ctx, 0)),
tmp_tv_cache,
not_found_is_qvar,
)?;
let len = if let Some(len) = args.next() {
self.instantiate_const_expr(
&len.expr,
None,
Some((ctx, 1)),
tmp_tv_cache,
not_found_is_qvar,
)?
@ -824,21 +828,24 @@ impl Context {
self.instantiate_acc(acc, erased_idx, tmp_tv_cache, not_found_is_qvar)
}
ast::ConstExpr::App(app) => {
let name = match &app.acc {
ast::ConstAccessor::Local(local) => local.inspect(),
_ => return type_feature_error!(self, app.loc(), "instantiating const callee"),
let ast::ConstAccessor::Local(ident) = &app.acc else {
return type_feature_error!(self, app.loc(), "instantiating const callee");
};
let &ctx = self
.get_singular_ctxs_by_ident(ident, self)?
.first()
.unwrap_or(&self);
let mut args = vec![];
for (i, arg) in app.args.pos_args().enumerate() {
let arg_t = self.instantiate_const_expr(
&arg.expr,
Some((self, i)),
Some((ctx, i)),
tmp_tv_cache,
not_found_is_qvar,
)?;
args.push(arg_t);
}
Ok(TyParam::app(name.clone(), args))
Ok(TyParam::app(ident.inspect().clone(), args))
}
ast::ConstExpr::Array(ConstArray::Normal(array)) => {
let mut tp_arr = vec![];

View file

@ -319,6 +319,10 @@ impl ContextKind {
matches!(self, Self::MethodDefs(_))
}
pub const fn is_trait_impl(&self) -> bool {
matches!(self, Self::MethodDefs(Some(_)))
}
pub const fn is_type(&self) -> bool {
matches!(self, Self::Class | Self::Trait | Self::StructuralTrait)
}

View file

@ -9,6 +9,7 @@ use std::time::{Duration, SystemTime};
use erg_common::config::ErgMode;
use erg_common::consts::{ERG_MODE, PYTHON_MODE};
use erg_common::dict::Dict;
use erg_common::env::{is_pystd_main_module, is_std_decl_path};
use erg_common::erg_util::BUILTIN_ERG_MODS;
use erg_common::levenshtein::get_similar_name;
@ -32,7 +33,7 @@ use crate::ty::free::{Constraint, HasLevel};
use crate::ty::typaram::TyParam;
use crate::ty::value::{GenTypeObj, TypeObj, ValueObj};
use crate::ty::{
GuardType, HasType, ParamTy, SubrType, Type, Variable, Visibility, VisibilityModifier,
Field, GuardType, HasType, ParamTy, SubrType, Type, Variable, Visibility, VisibilityModifier,
};
use crate::build_hir::HIRBuilder;
@ -200,7 +201,10 @@ impl Context {
} else {
None
};
if let Some(_decl) = self.decls.remove(&ident.name) {
if self
.remove_class_attr(ident.name.inspect())
.is_some_and(|(_, decl)| !decl.kind.is_auto())
{
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
@ -266,7 +270,10 @@ impl Context {
self.absolutize(sig.ident.name.loc()),
);
self.index().register(&vi);
if let Some(_decl) = self.decls.remove(name) {
if self
.remove_class_attr(name)
.is_some_and(|(_, decl)| !decl.kind.is_auto())
{
Err(TyCheckErrors::from(TyCheckError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
@ -452,6 +459,7 @@ impl Context {
if self
.registered_info(name.inspect(), name.is_const())
.is_some()
&& &name.inspect()[..] != "_"
{
Err(TyCheckErrors::from(TyCheckError::reassign_error(
self.cfg.input.clone(),
@ -1287,9 +1295,7 @@ impl Context {
2,
self.level,
);
if ERG_MODE {
self.gen_class_new_method(&gen, &mut ctx)?;
}
self.gen_class_new_method(&gen, &mut ctx)?;
self.register_gen_mono_type(ident, gen, ctx, Const)
} else {
let params = gen
@ -1306,9 +1312,7 @@ impl Context {
2,
self.level,
);
if ERG_MODE {
self.gen_class_new_method(&gen, &mut ctx)?;
}
self.gen_class_new_method(&gen, &mut ctx)?;
self.register_gen_poly_type(ident, gen, ctx, Const)
}
}
@ -1350,16 +1354,7 @@ impl Context {
..
} = additional
{
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls.insert(varname, vi);
}
self.register_instance_attrs(&mut ctx, rec)?;
}
param_t
.map(|t| self.intersection(t, additional.typ()))
@ -1421,16 +1416,7 @@ impl Context {
self.level,
);
let Some(TypeObj::Builtin{ t: Type::Record(req), .. }) = gen.base_or_sup() else { todo!("{gen}") };
for (field, t) in req.iter() {
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls
.insert(VarName::from_str(field.symbol.clone()), vi);
}
self.register_instance_attrs(&mut ctx, req)?;
self.register_gen_mono_type(ident, gen, ctx, Const)
} else {
feature_error!(
@ -1463,16 +1449,7 @@ impl Context {
None
};
if let Some(additional) = additional {
for (field, t) in additional.iter() {
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls
.insert(VarName::from_str(field.symbol.clone()), vi);
}
self.register_instance_attrs(&mut ctx, additional)?;
}
for sup in super_classes.into_iter() {
if let Some((_, sup_ctx)) = self.get_nominal_type_ctx(&sup) {
@ -1524,6 +1501,29 @@ impl Context {
}
}
fn register_instance_attrs(
&self,
ctx: &mut Context,
rec: &Dict<Field, Type>,
) -> CompileResult<()> {
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi =
VarInfo::instance_attr(field.clone(), t.clone(), self.impl_of(), ctx.name.clone());
// self.index().register(&vi);
if let Some(_ent) = ctx.decls.insert(varname.clone(), vi) {
return Err(CompileErrors::from(CompileError::duplicate_decl_error(
self.cfg.input.clone(),
line!() as usize,
varname.loc(),
self.caused_by(),
varname.inspect(),
)));
}
}
Ok(())
}
fn gen_class_new_method(&self, gen: &GenTypeObj, ctx: &mut Context) -> CompileResult<()> {
let mut methods = Self::methods(None, self.cfg.clone(), self.shared.clone(), 2, self.level);
let new_t = if let Some(base) = gen.base_or_sup() {
@ -1532,16 +1532,7 @@ impl Context {
t: Type::Record(rec),
..
} => {
for (field, t) in rec.iter() {
let varname = VarName::from_str(field.symbol.clone());
let vi = VarInfo::instance_attr(
field.clone(),
t.clone(),
self.impl_of(),
ctx.name.clone(),
);
ctx.decls.insert(varname, vi);
}
self.register_instance_attrs(ctx, rec)?;
}
other => {
methods.register_fixed_auto_impl(
@ -1557,16 +1548,31 @@ impl Context {
} else {
func0(gen.typ().clone())
};
methods.register_fixed_auto_impl(
"__new__",
new_t.clone(),
Immutable,
Visibility::BUILTIN_PRIVATE,
Some("__call__".into()),
)?;
// 必要なら、ユーザーが独自に上書きする
// users can override this if necessary
methods.register_auto_impl("new", new_t, Immutable, Visibility::BUILTIN_PUBLIC, None)?;
if ERG_MODE {
methods.register_fixed_auto_impl(
"__new__",
new_t.clone(),
Immutable,
Visibility::BUILTIN_PRIVATE,
Some("__call__".into()),
)?;
// users can override this if necessary
methods.register_auto_impl(
"new",
new_t,
Immutable,
Visibility::BUILTIN_PUBLIC,
None,
)?;
} else {
methods.register_auto_impl(
"__call__",
new_t,
Immutable,
Visibility::BUILTIN_PUBLIC,
Some("__call__".into()),
)?;
}
ctx.methods_list
.push((ClassDefType::Simple(gen.typ().clone()), methods));
Ok(())

View file

@ -446,11 +446,13 @@ impl Context {
Ok(())
}
(TyParam::Dict(sub), TyParam::Dict(sup)) => {
for (lk, lv) in sub.iter() {
if let Some(rv) = sup.get(lk).or_else(|| sub_tpdict_get(sup, lk, self)) {
self.sub_unify_tp(lv, rv, _variance, loc, allow_divergence)?;
for (sub_k, sub_v) in sub.iter() {
if let Some(sup_v) = sup.get(sub_k).or_else(|| sub_tpdict_get(sup, sub_k, self))
{
// self.sub_unify_tp(sub_k, sup_k, _variance, loc, allow_divergence)?;
self.sub_unify_tp(sub_v, sup_v, _variance, loc, allow_divergence)?;
} else {
log!(err "{sup} does not have key {lk}");
log!(err "{sup} does not have key {sub_k}");
// TODO:
return Err(TyCheckErrors::from(TyCheckError::unreachable(
self.cfg.input.clone(),

View file

@ -177,7 +177,7 @@ impl ASTLowerer {
for ctx in ctxs {
if let Triple::Ok(vi) = ctx.rec_get_var_info(
&ident.raw,
AccessKind::Attr,
AccessKind::UnboundAttr,
self.input(),
&self.module.context,
) {

View file

@ -1,5 +1,5 @@
use erg_common::config::Input;
use erg_common::error::{ErrorCore, ErrorKind::*, Location, SubMessage};
use erg_common::io::Input;
use erg_common::switch_lang;
use crate::error::*;

View file

@ -1,5 +1,5 @@
use erg_common::config::Input;
use erg_common::error::{ErrorCore, ErrorKind::*, Location, SubMessage};
use erg_common::io::Input;
use erg_common::style::{StyledStr, StyledString, StyledStrings, Stylize};
use erg_common::traits::Locational;
use erg_common::{switch_lang, Str};
@ -485,11 +485,12 @@ impl LowerError {
) -> Self {
let hint = similar_name.map(|n| {
let vis = similar_info.map_or("".into(), |vi| vi.vis.modifier.display());
let kind = similar_info.map_or("", |vi| vi.kind.display());
switch_lang!(
"japanese" => format!("似た名前の{vis}属性があります: {n}"),
"simplified_chinese" => format!("具有相同名称的{vis}属性: {n}"),
"traditional_chinese" => format!("具有相同名稱的{vis}屬性: {n}"),
"english" => format!("has a similar name {vis} attribute: {n}"),
"japanese" => format!("似た名前の{vis}{kind}属性があります: {n}"),
"simplified_chinese" => format!("具有相同名称的{vis}{kind}属性: {n}"),
"traditional_chinese" => format!("具有相同名稱的{vis}{kind}屬性: {n}"),
"english" => format!("has a similar name {vis} {kind} attribute: {n}"),
)
});
let found = StyledString::new(name, Some(ERR), Some(ATTR));
@ -1157,4 +1158,30 @@ impl LowerWarning {
caused_by,
)
}
pub fn same_name_instance_attr_warning(
input: Input,
errno: usize,
loc: Location,
caused_by: String,
name: &str,
) -> Self {
let name = StyledStr::new(readable_name(name), Some(WARN), Some(ATTR));
Self::new(
ErrorCore::new(
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => format!("同名のインスタンス属性{name}が存在します"),
"simplified_chinese" => format!("同名的实例属性{name}已存在"),
"traditional_chinese" => format!("同名的實例屬性{name}已存在"),
"english" => format!("an instance attribute named {name} already exists"),
),
errno,
NameWarning,
loc,
),
input,
caused_by,
)
}
}

View file

@ -4,15 +4,15 @@ pub mod tycheck;
use std::fmt;
use erg_common::config::Input;
use erg_common::error::{
ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay, SubMessage,
};
use erg_common::io::Input;
use erg_common::style::{Attribute, Color, StyledStr, StyledString, StyledStrings, Theme, THEME};
use erg_common::traits::{Locational, Stream};
use erg_common::{impl_display_and_error, impl_stream, switch_lang};
use erg_parser::error::{ParserRunnerError, ParserRunnerErrors};
use erg_parser::error::{ParseError, ParseErrors, ParserRunnerError, ParserRunnerErrors};
pub use crate::error::eval::*;
pub use crate::error::lower::*;
@ -497,6 +497,18 @@ impl From<CompileError> for CompileErrors {
}
}
impl From<CompileError> for ParseError {
fn from(err: CompileError) -> Self {
Self::new(*err.core)
}
}
impl From<CompileErrors> for ParseErrors {
fn from(err: CompileErrors) -> Self {
Self::new(err.into_iter().map(ParseError::from).collect())
}
}
impl MultiErrorDisplay<CompileError> for CompileErrors {}
impl fmt::Display for CompileErrors {
@ -524,7 +536,7 @@ mod test {
ty::{Predicate, Type},
varinfo::{AbsLocation, VarInfo},
};
use erg_common::{config::Input, error::Location};
use erg_common::{error::Location, io::Input};
use erg_parser::ast::{VarName, VisModifierSpec};
// These Erg codes are not correct grammar.

View file

@ -1,7 +1,7 @@
use std::fmt::Display;
use erg_common::config::Input;
use erg_common::error::{ErrorCore, ErrorKind::*, Location, SubMessage};
use erg_common::io::Input;
use erg_common::set::Set;
use erg_common::style::{StyledStr, StyledString, StyledStrings, Stylize};
use erg_common::traits::{Locational, NoTypeDisplay};

View file

@ -8,6 +8,9 @@
dtype: Type
size: Nat
.nan: Float
.Nan: Float
.abs: |T|(object: .NDArray(T),) -> .NDArray(T)
.add: |T|(object: .NDArray(T), other: .NDArray(T)) -> .NDArray(T)
.all: |T <: Num|(object: .NDArray(T),) -> Bool

View file

View file

@ -0,0 +1,5 @@
{
.DataFrame!;
.Series!;
.Index;
} = pyimport "core/api"

View file

@ -0,0 +1,3 @@
{.DataFrame!;} = pyimport "./frame"
{.Series!;} = pyimport "./series"
{.Index;} = pyimport "./indexes/api"

View file

@ -0,0 +1,14 @@
{.Index;} = pyimport "./indexes/api"
# I := Nat, V := Obj
.DataFrame!: (C: Type, I: Type, V: Type) -> ClassType
.DataFrame!(C, I, V) <: Input(C)
.DataFrame!(C, I, V) <: Input(I)
.DataFrame!(C, I, V) <: Output(V)
.DataFrame!.
__call__: |K, V, I|(dic: {K: [V; _]} or Iterable(Iterable(V)), index: [I; _] := [Nat; _]) -> .DataFrame!(K, I, V)
shape: (Nat, Nat)
index: .Index(_) # TODO
head: |C, I, V|(self: .DataFrame!(C, I, V), tail: Nat := {5}) -> .DataFrame!(C, I, V)
tail: |C, I, V|(self: .DataFrame!(C, I, V), tail: Nat := {5}) -> .DataFrame!(C, I, V)
info!: (self: .DataFrame!(_, _, _)) => NoneType

View file

@ -0,0 +1 @@
{.Index;} = pyimport "./base"

View file

@ -0,0 +1,2 @@
.Index: (T: Type) -> ClassType
.Index(T) <: Output(T)

View file

@ -0,0 +1,8 @@
{.Index;} = pyimport "./indexes/api"
# K := Nat, V := Obj
.Series!: (K: Type, V: Type) -> ClassType
.Series!(K, V) <: Input(K)
.Series!(K, V) <: Output(V)
.Series!.
__call__: |K, V|(iterable: Iterable(V), index: [K; _] or .Index(K) := [Nat; _]) -> .Series! K, V

View file

View file

View file

View file

View file

@ -215,12 +215,12 @@ impl ASTLowerer {
format!("{}{code}", "\n".repeat(first_line as usize))
};
match ASTBuilder::new(self.cfg().clone()).build(code) {
Ok(ast) => {
self.check_doc_ast(ast);
Ok(artifact) => {
self.check_doc_ast(artifact.ast);
}
Err(errs) => {
let errs = CompileErrors::from(errs);
self.errs.extend(errs);
Err(iart) => {
self.errs.extend(CompileErrors::from(iart.errors));
self.warns.extend(CompileErrors::from(iart.warns));
}
}
}

View file

@ -37,8 +37,8 @@ use crate::context::{
RegistrationMode, TraitImpl,
};
use crate::error::{
CompileError, CompileErrors, LowerError, LowerErrors, LowerResult, LowerWarning, LowerWarnings,
SingleLowerResult,
CompileError, CompileErrors, CompileWarning, LowerError, LowerErrors, LowerResult,
LowerWarning, LowerWarnings, SingleLowerResult,
};
use crate::hir;
use crate::hir::HIR;
@ -125,22 +125,27 @@ impl Runnable for ASTLowerer {
fn exec(&mut self) -> Result<ExitStatus, Self::Errs> {
let mut ast_builder = ASTBuilder::new(self.cfg.copy());
let ast = ast_builder.build(self.cfg.input.read())?;
let artifact = self
.lower(ast, "exec")
let artifact = ast_builder
.build(self.cfg.input.read())
.map_err(|artifact| artifact.errors)?;
artifact.warns.fmt_all_stderr();
println!("{}", artifact.object);
artifact.warns.write_all_to(&mut self.cfg.output);
let artifact = self
.lower(artifact.ast, "exec")
.map_err(|artifact| artifact.errors)?;
artifact.warns.write_all_to(&mut self.cfg.output);
use std::io::Write;
write!(self.cfg.output, "{}", artifact.object).unwrap();
Ok(ExitStatus::compile_passed(artifact.warns.len()))
}
fn eval(&mut self, src: String) -> Result<String, Self::Errs> {
let mut ast_builder = ASTBuilder::new(self.cfg.copy());
let ast = ast_builder.build(src)?;
let artifact = ast_builder.build(src).map_err(|artifact| artifact.errors)?;
artifact.warns.write_all_stderr();
let artifact = self
.lower(ast, "eval")
.lower(artifact.ast, "eval")
.map_err(|artifact| artifact.errors)?;
artifact.warns.fmt_all_stderr();
artifact.warns.write_all_stderr();
Ok(format!("{}", artifact.object))
}
}
@ -1663,6 +1668,23 @@ impl ASTLowerer {
self.pop_append_errs();
errs
})?;
if let Some(ident) = def.sig.ident() {
if self
.module
.context
.get_instance_attr(ident.inspect())
.is_some()
{
self.warns
.push(CompileWarning::same_name_instance_attr_warning(
self.cfg.input.clone(),
line!() as usize,
ident.loc(),
self.module.context.caused_by(),
ident.inspect(),
));
}
}
}
ast::ClassAttr::Decl(_) | ast::ClassAttr::Doc(_) => {}
}
@ -1730,21 +1752,19 @@ impl ASTLowerer {
if let Some(sup_type) = call.args.get_left_or_key("Super") {
Self::check_inheritable(&self.cfg, &mut self.errs, type_obj, sup_type, &hir_def.sig);
}
let (__new__, need_to_gen_new) = if let (Some(dunder_new_vi), Some(new_vi)) = (
class_ctx.get_current_scope_var(&VarName::from_static("__new__")),
class_ctx.get_current_scope_var(&VarName::from_static("new")),
) {
(dunder_new_vi.t.clone(), new_vi.kind == VarKind::Auto)
} else {
let Some(__new__) = class_ctx.get_current_scope_var(&VarName::from_static("__new__")).or(class_ctx.get_current_scope_var(&VarName::from_static("__call__"))) else {
return unreachable_error!(LowerErrors, LowerError, self);
};
let need_to_gen_new = class_ctx
.get_current_scope_var(&VarName::from_static("new"))
.map_or(false, |vi| vi.kind == VarKind::Auto);
let require_or_sup = Self::get_require_or_sup_or_base(hir_def.body.block.remove(0));
Ok(hir::ClassDef::new(
type_obj.clone(),
hir_def.sig,
require_or_sup,
need_to_gen_new,
__new__,
__new__.t.clone(),
hir_methods,
))
}

View file

@ -1,5 +1,6 @@
use erg_common::config::ErgConfig;
use erg_common::error::MultiErrorDisplay;
use erg_common::io::Output;
use erg_common::spawn::exec_new_thread;
use erg_common::traits::Runnable;
@ -13,7 +14,8 @@ use erg_compiler::ty::constructors::{
use erg_compiler::ty::Type::*;
fn load_file(path: &'static str) -> Result<ModuleContext, CompileErrors> {
let cfg = ErgConfig::with_main_path(path.into());
let mut cfg = ErgConfig::with_main_path(path.into());
cfg.output = Output::Null;
let mut lowerer = ASTLowerer::new(cfg);
lowerer.exec()?;
Ok(lowerer.pop_mod_ctx().unwrap())
@ -26,7 +28,7 @@ fn test_infer_types() -> Result<(), ()> {
fn _test_infer_types() -> Result<(), ()> {
let module = load_file("tests/infer.er").map_err(|errs| {
errs.fmt_all_stderr();
errs.write_all_stderr();
})?;
let t = type_q("T");
let u = type_q("U");

View file

@ -148,10 +148,10 @@ impl Runnable for Transpiler {
path.set_extension("py");
let src = self.cfg.input.read();
let artifact = self.transpile(src, "exec").map_err(|eart| {
eart.warns.fmt_all_stderr();
eart.warns.write_all_stderr();
eart.errors
})?;
artifact.warns.fmt_all_stderr();
artifact.warns.write_all_stderr();
let mut f = File::create(path).unwrap();
f.write_all(artifact.object.code.as_bytes()).unwrap();
Ok(ExitStatus::compile_passed(artifact.warns.len()))
@ -159,10 +159,10 @@ impl Runnable for Transpiler {
fn eval(&mut self, src: String) -> Result<String, CompileErrors> {
let artifact = self.transpile(src, "eval").map_err(|eart| {
eart.warns.fmt_all_stderr();
eart.warns.write_all_stderr();
eart.errors
})?;
artifact.warns.fmt_all_stderr();
artifact.warns.write_all_stderr();
Ok(artifact.object.code)
}
}

View file

@ -444,6 +444,22 @@ impl SubrType {
|| self.return_t.has_qvar()
}
pub fn has_undoable_linked_var(&self) -> bool {
self.non_default_params
.iter()
.any(|pt| pt.typ().has_undoable_linked_var())
|| self
.var_params
.as_ref()
.map(|pt| pt.typ().has_undoable_linked_var())
.unwrap_or(false)
|| self
.default_params
.iter()
.any(|pt| pt.typ().has_undoable_linked_var())
|| self.return_t.has_undoable_linked_var()
}
pub fn typarams(&self) -> Vec<TyParam> {
[
self.non_default_params
@ -2450,6 +2466,58 @@ impl Type {
}
}
pub fn has_undoable_linked_var(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_undoable_linked() => true,
Self::FreeVar(fv) if fv.is_linked() => fv.crack().has_undoable_linked_var(),
Self::FreeVar(fv) => {
if let Some((sub, sup)) = fv.get_subsup() {
fv.dummy_link();
let res_sub = sub.has_undoable_linked_var();
let res_sup = sup.has_undoable_linked_var();
fv.undo();
res_sub || res_sup
} else {
let opt_t = fv.get_type();
opt_t.map_or(false, |t| t.has_undoable_linked_var())
}
}
Self::Ref(ty) => ty.has_undoable_linked_var(),
Self::RefMut { before, after } => {
before.has_undoable_linked_var()
|| after
.as_ref()
.map(|t| t.has_undoable_linked_var())
.unwrap_or(false)
}
Self::And(lhs, rhs) | Self::Or(lhs, rhs) => {
lhs.has_undoable_linked_var() || rhs.has_undoable_linked_var()
}
Self::Not(ty) => ty.has_undoable_linked_var(),
Self::Callable { param_ts, return_t } => {
param_ts.iter().any(|t| t.has_undoable_linked_var())
|| return_t.has_undoable_linked_var()
}
Self::Subr(subr) => subr.has_undoable_linked_var(),
Self::Quantified(quant) => quant.has_undoable_linked_var(),
Self::Record(r) => r.values().any(|t| t.has_undoable_linked_var()),
Self::Refinement(refine) => {
refine.t.has_undoable_linked_var() || refine.pred.has_undoable_linked_var()
}
Self::Poly { params, .. } => params.iter().any(|tp| tp.has_undoable_linked_var()),
Self::Proj { lhs, .. } => lhs.has_undoable_linked_var(),
Self::ProjCall { lhs, args, .. } => {
lhs.has_undoable_linked_var() || args.iter().any(|tp| tp.has_undoable_linked_var())
}
Self::Structural(ty) => ty.has_undoable_linked_var(),
Self::Guard(guard) => guard.to.has_undoable_linked_var(),
Self::Bounded { sub, sup } => {
sub.has_undoable_linked_var() || sup.has_undoable_linked_var()
}
_ => false,
}
}
pub fn has_no_qvar(&self) -> bool {
!self.has_qvar()
}

View file

@ -341,6 +341,21 @@ impl Predicate {
}
}
pub fn has_undoable_linked_var(&self) -> bool {
match self {
Self::Value(_) => false,
Self::Const(_) => false,
Self::Equal { rhs, .. }
| Self::GreaterEqual { rhs, .. }
| Self::LessEqual { rhs, .. }
| Self::NotEqual { rhs, .. } => rhs.has_undoable_linked_var(),
Self::Or(lhs, rhs) | Self::And(lhs, rhs) => {
lhs.has_undoable_linked_var() || rhs.has_undoable_linked_var()
}
Self::Not(pred) => pred.has_undoable_linked_var(),
}
}
pub fn min_max<'a>(
&'a self,
min: Option<&'a TyParam>,

View file

@ -1062,6 +1062,29 @@ impl TyParam {
!self.has_unbound_var()
}
pub fn has_undoable_linked_var(&self) -> bool {
match self {
Self::FreeVar(fv) => fv.is_undoable_linked(),
Self::Type(t) => t.has_undoable_linked_var(),
Self::Proj { obj, .. } => obj.has_undoable_linked_var(),
Self::Array(ts) | Self::Tuple(ts) => ts.iter().any(|t| t.has_undoable_linked_var()),
Self::Set(ts) => ts.iter().any(|t| t.has_undoable_linked_var()),
Self::Dict(kv) => kv
.iter()
.any(|(k, v)| k.has_undoable_linked_var() || v.has_undoable_linked_var()),
Self::Record(rec) => rec.iter().any(|(_, v)| v.has_undoable_linked_var()),
Self::Lambda(lambda) => lambda.body.iter().any(|t| t.has_undoable_linked_var()),
Self::UnaryOp { val, .. } => val.has_undoable_linked_var(),
Self::BinOp { lhs, rhs, .. } => {
lhs.has_undoable_linked_var() || rhs.has_undoable_linked_var()
}
Self::App { args, .. } => args.iter().any(|p| p.has_undoable_linked_var()),
Self::Erased(t) => t.has_undoable_linked_var(),
Self::Value(ValueObj::Type(t)) => t.typ().has_undoable_linked_var(),
_ => false,
}
}
pub fn union_size(&self) -> usize {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().union_size(),

View file

@ -7,10 +7,10 @@ use std::hash::{Hash, Hasher};
use std::ops::Neg;
use std::sync::Arc;
use erg_common::config::Input;
use erg_common::dict::Dict;
use erg_common::error::{ErrorCore, ErrorKind, Location};
use erg_common::fresh::fresh_varname;
use erg_common::io::Input;
use erg_common::python_util::PythonVersion;
use erg_common::serialize::*;
use erg_common::set::Set;

View file

@ -3,7 +3,7 @@ use std::path::PathBuf;
use erg_common::error::Location;
use erg_common::set::Set;
use erg_common::Str;
use erg_common::{switch_lang, Str};
use erg_parser::ast::DefId;
@ -39,6 +39,7 @@ use Mutability::*;
pub enum VarKind {
Defined(DefId),
Declared,
InstanceAttr,
Parameter {
def_id: DefId,
var: bool,
@ -88,6 +89,38 @@ impl VarKind {
pub const fn is_builtin(&self) -> bool {
matches!(self, Self::Builtin)
}
pub const fn is_auto(&self) -> bool {
matches!(self, Self::Auto)
}
pub const fn is_instance_attr(&self) -> bool {
matches!(self, Self::InstanceAttr)
}
pub const fn display(&self) -> &'static str {
match self {
Self::Auto | Self::FixedAuto => switch_lang!(
"japanese" => "自動",
"simplified_chinese" => "自动",
"traditional_chinese" => "自動",
"english" => "auto",
),
Self::Builtin => switch_lang!(
"japanese" => "組み込み",
"simplified_chinese" => "内置",
"traditional_chinese" => "內置",
"english" => "builtin",
),
Self::InstanceAttr => switch_lang!(
"japanese" => "インスタンス",
"simplified_chinese" => "实例",
"traditional_chinese" => "實例",
"english" => "instance",
),
_ => "",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -285,12 +318,11 @@ impl VarInfo {
} else {
Mutability::Immutable
};
let kind = VarKind::Declared;
Self::new(
t,
muty,
Visibility::new(field.vis, namespace),
kind,
VarKind::InstanceAttr,
None,
impl_of,
None,

View file

@ -1004,6 +1004,15 @@ impl_nested_display_for_enum!(RecordAttrOrIdent; Attr, Ident);
impl_display_for_enum!(RecordAttrOrIdent; Attr, Ident);
impl_locational_for_enum!(RecordAttrOrIdent; Attr, Ident);
impl RecordAttrOrIdent {
pub fn ident(&self) -> Option<&Identifier> {
match self {
Self::Attr(attr) => attr.sig.ident(),
Self::Ident(ident) => Some(ident),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct NormalSet {
pub l_brace: Token,

View file

@ -4,7 +4,7 @@ use erg_common::Str;
use crate::ast::AST;
use crate::desugar::Desugarer;
use crate::error::{ParserRunnerError, ParserRunnerErrors};
use crate::error::{CompleteArtifact, IncompleteArtifact, ParserRunnerError, ParserRunnerErrors};
use crate::parse::ParserRunner;
/// Summarize parsing and desugaring
@ -45,31 +45,55 @@ impl Runnable for ASTBuilder {
fn exec(&mut self) -> Result<ExitStatus, Self::Errs> {
let src = self.cfg_mut().input.read();
let ast = self.build(src)?;
println!("{ast}");
let artifact = self.build(src).map_err(|iart| iart.errors)?;
println!("{}", artifact.ast);
Ok(ExitStatus::OK)
}
fn eval(&mut self, src: String) -> Result<String, ParserRunnerErrors> {
let ast = self.build(src)?;
Ok(format!("{ast}"))
let artifact = self.build(src).map_err(|iart| iart.errors)?;
Ok(format!("{}", artifact.ast))
}
}
impl ASTBuilder {
pub fn build(&mut self, src: String) -> Result<AST, ParserRunnerErrors> {
let module = self.runner.parse(src)?;
pub fn build(
&mut self,
src: String,
) -> Result<
CompleteArtifact<AST, ParserRunnerErrors>,
IncompleteArtifact<AST, ParserRunnerErrors>,
> {
let name = Str::rc(self.runner.cfg().input.unescaped_filename());
let artifact = self
.runner
.parse(src)
.map_err(|iart| iart.map_mod(|module| AST::new(name.clone(), module)))?;
let mut desugarer = Desugarer::new();
let module = desugarer.desugar(module);
let name = self.runner.cfg().input.unescaped_filename();
let ast = AST::new(Str::rc(name), module);
Ok(ast)
let module = desugarer.desugar(artifact.ast);
let ast = AST::new(name, module);
Ok(CompleteArtifact::new(
ast,
ParserRunnerErrors::convert(self.input(), artifact.warns),
))
}
pub fn build_without_desugaring(&mut self, src: String) -> Result<AST, ParserRunnerErrors> {
let module = self.runner.parse(src)?;
let name = self.runner.cfg().input.unescaped_filename();
let ast = AST::new(Str::rc(name), module);
Ok(ast)
pub fn build_without_desugaring(
&mut self,
src: String,
) -> Result<
CompleteArtifact<AST, ParserRunnerErrors>,
IncompleteArtifact<AST, ParserRunnerErrors>,
> {
let name = Str::rc(self.runner.cfg().input.unescaped_filename());
let artifact = self
.runner
.parse(src)
.map_err(|iart| iart.map_mod(|module| AST::new(name.clone(), module)))?;
let ast = AST::new(name, artifact.ast);
Ok(CompleteArtifact::new(
ast,
ParserRunnerErrors::convert(self.input(), artifact.warns),
))
}
}

View file

@ -3,14 +3,15 @@
//! パーサーが出すエラーを定義
use std::fmt;
use erg_common::config::Input;
use erg_common::error::{
ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay, SubMessage,
};
use erg_common::style::{Attribute, Color, StyledStr, StyledStrings, THEME};
use erg_common::io::Input;
use erg_common::style::{Attribute, Color, StyledStr, StyledString, StyledStrings, THEME};
use erg_common::traits::Stream;
use erg_common::{fmt_iter, fmt_vec_split_with, impl_display_and_error, impl_stream, switch_lang};
use crate::ast::Module;
use crate::token::TokenKind;
#[derive(Debug)]
@ -50,6 +51,7 @@ impl fmt::Display for LexErrors {
impl std::error::Error for LexErrors {}
const ERR: Color = THEME.colors.error;
const WARN: Color = THEME.colors.warning;
const HINT: Color = THEME.colors.hint;
#[cfg(not(feature = "pretty"))]
const ATTR: Attribute = Attribute::Bold;
@ -534,12 +536,30 @@ impl LexError {
);
Self::syntax_error(errno, loc, msg, None)
}
pub fn duplicate_elem_warning(errno: usize, loc: Location, elem: String) -> Self {
let elem = StyledString::new(elem, Some(WARN), Some(Attribute::Underline));
Self::new(ErrorCore::new(
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => format!("重複する要素です: {elem}"),
"simplified_chinese" => format!("{elem}"),
"traditional_chinese" => format!("{elem}"),
"english" => format!("duplicated element: {elem}"),
),
errno,
SyntaxWarning,
loc,
))
}
}
pub type LexResult<T> = Result<T, LexError>;
pub type ParseError = LexError;
pub type ParseErrors = LexErrors;
pub type ParseWarning = LexError;
pub type ParseWarnings = LexErrors;
pub type ParseResult<T> = Result<T, ()>;
#[derive(Debug)]
@ -618,4 +638,71 @@ pub type ParserRunnerResult<T> = Result<T, ParserRunnerError>;
pub type LexerRunnerError = ParserRunnerError;
pub type LexerRunnerErrors = ParserRunnerErrors;
pub type ParserRunnerWarning = ParserRunnerError;
pub type ParserRunnerWarnings = ParserRunnerErrors;
pub type LexerRunnerResult<T> = Result<T, LexerRunnerError>;
#[derive(Debug)]
pub struct CompleteArtifact<A = Module, Es = ParseErrors> {
pub ast: A,
pub warns: Es,
}
impl<A, Es> CompleteArtifact<A, Es> {
pub fn new(ast: A, warns: Es) -> Self {
Self { ast, warns }
}
}
#[derive(Debug)]
pub struct IncompleteArtifact<A = Module, Es = ParseErrors> {
pub ast: Option<A>,
pub warns: Es,
pub errors: Es,
}
impl<A> From<ParserRunnerErrors> for IncompleteArtifact<A, ParserRunnerErrors> {
fn from(value: ParserRunnerErrors) -> IncompleteArtifact<A, ParserRunnerErrors> {
IncompleteArtifact::new(None, ParserRunnerErrors::empty(), value)
}
}
impl<A> From<LexErrors> for IncompleteArtifact<A, ParseErrors> {
fn from(value: LexErrors) -> IncompleteArtifact<A, ParseErrors> {
IncompleteArtifact::new(None, ParseErrors::empty(), value)
}
}
impl<A, Es> IncompleteArtifact<A, Es> {
pub fn new(ast: Option<A>, warns: Es, errors: Es) -> Self {
Self { ast, warns, errors }
}
pub fn map_errs<U>(self, f: impl Fn(Es) -> U) -> IncompleteArtifact<A, U> {
IncompleteArtifact {
ast: self.ast,
warns: f(self.warns),
errors: f(self.errors),
}
}
pub fn map_mod<U>(self, f: impl Fn(A) -> U) -> IncompleteArtifact<U, Es> {
IncompleteArtifact {
ast: self.ast.map(f),
warns: self.warns,
errors: self.errors,
}
}
}
#[derive(Debug)]
pub struct ErrorArtifact<Es = ParseErrors> {
pub warns: Es,
pub errors: Es,
}
impl<Es> ErrorArtifact<Es> {
pub fn new(warns: Es, errors: Es) -> ErrorArtifact<Es> {
Self { warns, errors }
}
}

View file

@ -6,7 +6,7 @@ use unicode_xid::UnicodeXID;
use erg_common::cache::CacheSet;
use erg_common::config::ErgConfig;
use erg_common::config::Input;
use erg_common::io::Input;
use erg_common::traits::DequeStream;
use erg_common::traits::{Locational, Runnable, Stream};
use erg_common::{debug_power_assert, fn_name_full, normalize_newline, switch_lang};

View file

@ -5,8 +5,8 @@
use std::mem;
use erg_common::config::ErgConfig;
use erg_common::config::{Input, InputKind};
use erg_common::error::Location;
use erg_common::io::{Input, InputKind};
use erg_common::set::Set as HashSet;
use erg_common::str::Str;
use erg_common::traits::{DequeStream, ExitStatus, Locational, Runnable, Stream};
@ -17,7 +17,10 @@ use erg_common::{
use crate::ast::*;
use crate::desugar::Desugarer;
use crate::error::{ParseError, ParseErrors, ParseResult, ParserRunnerError, ParserRunnerErrors};
use crate::error::{
CompleteArtifact, IncompleteArtifact, ParseError, ParseErrors, ParseResult, ParserRunnerError,
ParserRunnerErrors,
};
use crate::lex::Lexer;
use crate::token::{Token, TokenCategory, TokenKind, TokenStream};
@ -96,13 +99,13 @@ macro_rules! expect_pop {
}
pub trait Parsable {
fn parse(code: String) -> Result<Module, ParseErrors>;
fn parse(code: String) -> Result<CompleteArtifact, IncompleteArtifact<Module, ParseErrors>>;
}
pub struct SimpleParser {}
impl Parsable for SimpleParser {
fn parse(code: String) -> Result<Module, ParseErrors> {
fn parse(code: String) -> Result<CompleteArtifact, IncompleteArtifact> {
let ts = Lexer::from_str(code).lex()?;
let mut parser = Parser::new(ts);
parser.parse()
@ -416,59 +419,77 @@ impl Runnable for ParserRunner {
fn exec(&mut self) -> Result<ExitStatus, Self::Errs> {
let src = self.cfg_mut().input.read();
let ast = self.parse(src)?;
println!("{ast}");
let artifact = self.parse(src).map_err(|iart| iart.errors)?;
println!("{}", artifact.ast);
Ok(ExitStatus::OK)
}
fn eval(&mut self, src: String) -> Result<String, ParserRunnerErrors> {
let ast = self.parse(src)?;
Ok(format!("{ast}"))
let artifact = self.parse(src).map_err(|iart| iart.errors)?;
Ok(format!("{}", artifact.ast))
}
}
impl ParserRunner {
pub fn parse_token_stream(&mut self, ts: TokenStream) -> Result<Module, ParserRunnerErrors> {
pub fn parse_token_stream(
&mut self,
ts: TokenStream,
) -> Result<CompleteArtifact, IncompleteArtifact<Module, ParserRunnerErrors>> {
Parser::new(ts)
.parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
.map_err(|iart| iart.map_errs(|errs| ParserRunnerErrors::convert(self.input(), errs)))
}
pub fn parse(&mut self, src: String) -> Result<Module, ParserRunnerErrors> {
pub fn parse(
&mut self,
src: String,
) -> Result<CompleteArtifact, IncompleteArtifact<Module, ParserRunnerErrors>> {
let ts = Lexer::new(Input::new(InputKind::Str(src), self.cfg.input.id()))
.lex()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))?;
Parser::new(ts)
.parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
.map_err(|iart| iart.map_errs(|errs| ParserRunnerErrors::convert(self.input(), errs)))
}
}
impl Parser {
pub fn parse(&mut self) -> Result<Module, ParseErrors> {
pub fn parse(&mut self) -> Result<CompleteArtifact, IncompleteArtifact> {
if self.tokens.is_empty() {
return Ok(Module::empty());
return Ok(CompleteArtifact::new(Module::empty(), ParseErrors::empty()));
}
log!(info "the parsing process has started.");
log!(info "token stream: {}", self.tokens);
let module = match self.try_reduce_module() {
Ok(module) => module,
Err(_) => {
return Err(mem::take(&mut self.errs));
return Err(IncompleteArtifact::new(
None,
mem::take(&mut self.warns),
mem::take(&mut self.errs),
));
}
};
if !self.cur_is(EOF) {
let loc = self.peek().map(|t| t.loc()).unwrap_or_default();
self.errs
.push(ParseError::compiler_bug(0, loc, fn_name!(), line!()));
return Err(mem::take(&mut self.errs));
return Err(IncompleteArtifact::new(
Some(module),
mem::take(&mut self.warns),
mem::take(&mut self.errs),
));
}
log!(info "the parsing process has completed (errs: {}).", self.errs.len());
log!(info "AST:\n{module}");
if self.errs.is_empty() {
Ok(module)
Ok(CompleteArtifact::new(module, mem::take(&mut self.warns)))
} else {
Err(mem::take(&mut self.errs))
Err(IncompleteArtifact::new(
Some(module),
mem::take(&mut self.warns),
mem::take(&mut self.errs),
))
}
}
@ -2839,6 +2860,17 @@ impl Parser {
})?;
match next {
Expr::Def(def) => {
if attrs.iter().any(|attr| {
attr.ident()
.zip(def.sig.ident())
.is_some_and(|(l, r)| l == r)
}) {
self.warns.push(ParseError::duplicate_elem_warning(
line!() as usize,
def.sig.loc(),
def.sig.to_string(),
));
}
attrs.push(RecordAttrOrIdent::Attr(def));
}
Expr::Accessor(acc) => {

View file

@ -1,83 +1,97 @@
use erg_common::config::{ErgConfig, Input};
use erg_common::config::ErgConfig;
use erg_common::consts::DEBUG_MODE;
use erg_common::error::MultiErrorDisplay;
use erg_common::io::Input;
use erg_common::spawn::exec_new_thread;
use erg_common::traits::{Runnable, Stream};
use erg_parser::error::ParserRunnerErrors;
use erg_parser::error::{ErrorArtifact, ParseWarnings, ParserRunnerErrors};
use erg_parser::lex::Lexer;
use erg_parser::ParserRunner;
#[test]
fn parse_args() -> Result<(), ()> {
expect_success("tests/args.er")
expect_success("tests/args.er", 0)
}
#[test]
fn parse_containers() -> Result<(), ()> {
expect_success("tests/containers.er")
expect_success("tests/containers.er", 0)
}
#[test]
fn parse_dependent() -> Result<(), ()> {
expect_success("tests/dependent.er")
expect_success("tests/dependent.er", 0)
}
#[test]
fn parse_fib() -> Result<(), ()> {
expect_success("tests/fib.er")
expect_success("tests/fib.er", 0)
}
#[test]
fn parse_hello_world() -> Result<(), ()> {
expect_success("tests/hello_world.er")
expect_success("tests/hello_world.er", 0)
}
#[test]
fn parse_simple_if() -> Result<(), ()> {
expect_success("tests/simple_if.er")
expect_success("tests/simple_if.er", 0)
}
#[test]
fn parse_stream() -> Result<(), ()> {
expect_success("tests/stream.er")
expect_success("tests/stream.er", 0)
}
#[test]
fn parse_test1_basic_syntax() -> Result<(), ()> {
expect_success("tests/test1_basic_syntax.er")
expect_success("tests/test1_basic_syntax.er", 0)
}
#[test]
fn parse_test2_advanced_syntax() -> Result<(), ()> {
expect_success("tests/test2_advanced_syntax.er")
expect_success("tests/test2_advanced_syntax.er", 0)
}
#[test]
fn parse_stack() -> Result<(), ()> {
expect_failure("tests/stack.er", 2)
expect_failure("tests/stack.er", 0, 2)
}
#[test]
fn parse_str_literal() -> Result<(), ()> {
expect_failure("tests/failed_str_lit.er", 2)
expect_failure("tests/failed_str_lit.er", 0, 2)
}
#[test]
fn parse_invalid_chunk() -> Result<(), ()> {
expect_failure("tests/invalid_chunk.er", 62)
expect_failure("tests/invalid_chunk.er", 0, 62)
}
#[test]
fn parse_invalid_collections() -> Result<(), ()> {
expect_failure("tests/invalid_collections.er", 29)
expect_failure("tests/invalid_collections.er", 0, 29)
}
#[test]
fn parse_invalid_class_definition() -> Result<(), ()> {
expect_failure("tests/invalid_class_definition.er", 7)
expect_failure("tests/invalid_class_definition.er", 0, 7)
}
fn _parse_test_from_code(file_path: &'static str) -> Result<(), ParserRunnerErrors> {
#[test]
fn exec_invalid_chunk_prs_err() -> Result<(), ()> {
expect_failure("tests/invalid_chunk.er", 0, 62)
}
#[test]
fn exec_warns() -> Result<(), ()> {
expect_success("tests/warns.er", 1)
}
fn _parse_test_from_code(
file_path: &'static str,
) -> Result<ParseWarnings, ErrorArtifact<ParserRunnerErrors>> {
let input = Input::file(file_path.into());
let cfg = ErgConfig {
input: input.clone(),
@ -86,43 +100,81 @@ fn _parse_test_from_code(file_path: &'static str) -> Result<(), ParserRunnerErro
};
let lexer = Lexer::new(input.clone());
let mut parser = ParserRunner::new(cfg);
match parser.parse_token_stream(
lexer
.lex()
.map_err(|errs| ParserRunnerErrors::convert(&input, errs))?,
) {
Ok(module) => {
println!("{module}");
Ok(())
match parser.parse_token_stream(lexer.lex().map_err(|errs| {
ErrorArtifact::new(
ParserRunnerErrors::empty(),
ParserRunnerErrors::convert(&input, errs),
)
})?) {
Ok(artifact) => {
if DEBUG_MODE {
println!("{}", artifact.ast);
}
Ok(artifact.warns)
}
Err(e) => {
e.fmt_all_stderr();
Err(e)
Err(artifact) => {
if DEBUG_MODE {
artifact.warns.write_all_stderr();
artifact.errors.write_all_stderr();
}
Err(ErrorArtifact::new(artifact.warns, artifact.errors))
}
}
}
fn parse_test_from_code(file_path: &'static str) -> Result<(), ParserRunnerErrors> {
fn parse_test_from_code(
file_path: &'static str,
) -> Result<ParseWarnings, ErrorArtifact<ParserRunnerErrors>> {
exec_new_thread(move || _parse_test_from_code(file_path), file_path)
}
fn expect_success(file_path: &'static str) -> Result<(), ()> {
fn expect_success(file_path: &'static str, num_warns: usize) -> Result<(), ()> {
match parse_test_from_code(file_path) {
Ok(_) => Ok(()),
Ok(warns) => {
if warns.len() == num_warns {
Ok(())
} else {
println!(
"err: number of warnings is not {num_warns} but {}",
warns.len()
);
Err(())
}
}
Err(_) => Err(()),
}
}
fn expect_failure(file_path: &'static str, errs_len: usize) -> Result<(), ()> {
fn expect_failure(file_path: &'static str, num_warns: usize, num_errs: usize) -> Result<(), ()> {
match parse_test_from_code(file_path) {
Ok(_) => Err(()),
Err(errs) => {
if errs.len() == errs_len {
Ok(())
} else {
println!("err: error length is not {errs_len} but {}", errs.len());
Err(eart) => match (eart.errors.len() == num_errs, eart.warns.len() == num_warns) {
(true, true) => Ok(()),
(true, false) => {
println!(
"err: number of warnings is not {num_warns} but {}",
eart.warns.len()
);
Err(())
}
}
(false, true) => {
println!(
"err: number of errors is not {num_errs} but {}",
eart.errors.len()
);
Err(())
}
(false, false) => {
println!(
"err: number of warnings is not {num_warns} but {}",
eart.warns.len()
);
println!(
"err: number of errors is not {num_errs} but {}",
eart.errors.len()
);
Err(())
}
},
}
}

View file

@ -1,6 +1,6 @@
use std::iter::Iterator;
use erg_common::config::Input;
use erg_common::io::Input;
// use erg_compiler::parser;

View file

@ -0,0 +1 @@
_ = { .foo = 1; .foo = 1 } # WARN