feat: prove elm object cache is complete

This commit is contained in:
YetAnotherMinion 2021-12-11 18:28:04 +00:00 committed by nobody
commit 8354d51ecb
Signed by: GrocerPublishAgent
GPG key ID: D460CD54A9E3AB86
7 changed files with 583 additions and 157 deletions

View file

@ -21,6 +21,7 @@ structopt = { version = "0.3" }
elm-project-utils = { path = "../../../infra/rust-elm-project-utils" }
tracing = { version = "0.1", features = [] }
rustc-hash = "1.1"
home = "0.5"
# Required to transpile view functions to Rust
genco = "0.15"

View file

@ -12,15 +12,16 @@ type alias Model =
, b : Array Int
}
view2 : String -> Svg msg
view2 model =
svg [] []
view : String -> Html msg
view model =
div []
[ text <| "Hello world" ++ model ]
view2 : String -> Svg msg
view2 model =
svg [] []
view3: Bytes -> Html msg
view3 model =
case

301
src/elm.rs Normal file
View file

@ -0,0 +1,301 @@
use crate::reporting::{CompilerError, Problem};
use elmi::DataBinary;
use std::collections::HashMap;
use std::fs::{self, canonicalize, metadata};
use std::io::{self, BufRead, Error, ErrorKind, Write};
use std::path::{Path, PathBuf};
use std::process::{Command};
use tracing::info_span;
pub fn make(file: &Path, debug: bool, verbosity: u64) -> Result<(), Problem> {
let mut command = Command::new("elm");
command.arg("make").arg("--output").arg("/dev/null");
if debug {
command.arg("--debug");
}
command.arg(file);
let span = info_span!("elm make target file");
let timing_guard = span.enter();
match command.output() {
Ok(output) => {
if !output.status.success() {
io::stderr().write_all(&output.stderr).unwrap();
io::stderr().write_all(&output.stdout).unwrap();
return Err(CompilerError::FailedBuildingFixture.into());
}
if verbosity > 0 {
io::stderr().write_all(&output.stderr).unwrap();
}
}
Err(_io_err) => {
// TODO handle this one
return Err(Problem::Wildcard("elm failed".into()));
}
}
drop(timing_guard);
Ok(())
}
pub fn load_interfaces(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interface>, Problem> {
let mut interfaces = HashMap::new();
let entries = fs::read_dir(&elm_cache_dir)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
for entry in entries {
let entry = entry.map_err(|io_err| {
CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
})?;
let path = entry.path();
if path.is_file() {
match path.extension() {
Some(ext) if ext == "elmi" => {
// step 4 load all the modules
let parse_span = info_span!("parse elmi");
let _parse_guard = parse_span.enter();
let load_span = info_span!("file reads");
let load_guard = load_span.enter();
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
drop(load_guard);
let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
if let Some(stem) = path.file_stem() {
// in theory the module name of the interface can be determined by
// the filename in elm 0.19.0 and 0.19.1
let module_name: String = stem
.to_string_lossy()
.split('-')
.collect::<Vec<&str>>()
.join(".");
interfaces.insert(module_name, i);
}
}
_ => (),
}
}
}
Ok(interfaces)
}
pub fn load_objects(elm_cache_dir: &Path) -> Result<HashMap<elmi::Global, elmi::Node>, Problem> {
let mut objects = HashMap::new();
let entries = fs::read_dir(&elm_cache_dir)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
for entry in entries {
let entry = entry.map_err(|io_err| {
CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
})?;
let path = entry.path();
if path.is_file() {
match path.file_name() {
Some(file_name) if file_name == "o.dat" => {
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
let (_remaining, opt_global_graph) = elmi::OptGlobalGraph::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
objects.extend(opt_global_graph.nodes.into_iter());
}
_ => {},
}
match path.extension() {
Some(ext) if ext == "elmo" => {
// step 4 load all the modules
let parse_span = info_span!("parse elmo");
let _parse_guard = parse_span.enter();
let load_span = info_span!("file reads");
let load_guard = load_span.enter();
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
drop(load_guard);
let (_remaining, opt_local_graph) = elmi::OptLocalGraph::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
objects.extend(opt_local_graph.nodes.into_iter());
}
_ => (),
}
}
}
Ok(objects)
}
pub struct SymbolLoader {
project_cache_directory: PathBuf,
global_cache_directory: PathBuf,
symbol_cache: HashMap<elmi::Global, elmi::Node>,
}
impl SymbolLoader {
pub fn try_load(&mut self, symbol: elmi::Global) -> Result<bool, Problem> {
if self.symbol_cache.contains_key(&symbol) {
return Ok(true);
}
let elmi::Global(
elmi::ModuleNameCanonical {
ref package,
ref module,
},
ref _identifier,
) = symbol;
if *package == elmi::PackageName::new("author", "project") {
let filename = module.0.split('.').collect::<Vec<&str>>().join("-");
let mut path = self.project_cache_directory.join(filename);
path.set_extension(".elmo");
} else {
let mut path = self.global_cache_directory.clone();
path.push(&package.author);
path.push(&package.project);
// TODO figure out the version required.
//path.push(version);
path.push("artifacts.dat");
}
Ok(false)
}
}
//pub fn load_symbol_table(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interface>, Problem> {
// // given a global symbol, calculate the folder that contains the cache file.
// let (_remaining, i) = OptLocalGraph::get(&data).unwrap();
//
// let mut interfaces = HashMap::new();
//
// let entries = fs::read_dir(&elm_cache_dir)
// .map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
//
// for entry in entries {
// let entry = entry.map_err(|io_err| {
// CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
// })?;
// let path = entry.path();
// if path.is_file() {
// match path.extension() {
// Some(ext) if ext == "elmi" => {
// // step 4 load all the modules
// let parse_span = info_span!("parse elmi");
// let _parse_guard = parse_span.enter();
//
// let load_span = info_span!("file reads");
// let load_guard = load_span.enter();
// let data = std::fs::read(&path)
// .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
// drop(load_guard);
//
// let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
// CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
// })?;
//
// if let Some(stem) = path.file_stem() {
// // in theory the module name of the interface can be determined by
// // the filename in elm 0.19.0 and 0.19.1
// let module_name: String = stem
// .to_string_lossy()
// .split('-')
// .collect::<Vec<&str>>()
// .join(".");
// interfaces.insert(module_name, i);
// }
// }
// _ => (),
// }
// }
// }
//
// Ok(interfaces)
//}
/// Look for the cargo.toml file that we are building to determine the root directory of the project
/// (so that we can locate the source files)
pub fn find_project_root<S: AsRef<Path>>(filename: S, search: S) -> io::Result<PathBuf> {
if !search.as_ref().is_dir() {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("`{}` is not a directory", search.as_ref().display()),
));
}
let mut path_buf = canonicalize(&search)?;
let error: io::Error = Error::new(
ErrorKind::NotFound,
format!(
"could not find `{}` in `{:?}` or any parent directory",
filename.as_ref().display(),
path_buf
),
);
loop {
let test_file = path_buf.as_path().join(&filename);
match metadata(test_file) {
Ok(_) => return Ok(path_buf),
Err(_) => (),
}
match path_buf.as_path().parent() {
Some(_) => (),
None => return Err(error),
}
path_buf.pop();
}
}
pub fn get_elm_home() -> io::Result<PathBuf> {
match std::env::var("ELM_HOME") {
Ok(elm_home) => Ok(PathBuf::from(elm_home)),
Err(_) => {
if let Some(mut dir) = home::home_dir() {
dir.push(".elm");
Ok(dir)
} else {
Err(std::io::Error::new(
ErrorKind::NotFound,
"ELM_HOME not set and home directory could not be calculated by `home` crate",
))
}
}
}
}
pub fn parse_module_name(data: &[u8]) -> Result<elmi::Name, CompilerError> {
if let Some(Ok(first_line)) = data.lines().next() {
let mut tokens = first_line.split_whitespace();
match tokens.next() {
Some(token) if token == "port" => {
tokens.next();
}
Some(token) if token == "module" => (),
Some(token) if token == "effect" => {
tokens.next();
}
_ => {
return Err(CompilerError::CantParseModule(first_line.clone()).into());
}
};
if let Some(module_name) = tokens.next() {
Ok(elmi::Name(module_name.to_string()))
} else {
Err(CompilerError::CantParseModule(first_line.clone()).into())
}
} else {
Err(CompilerError::EmptyModule.into())
}
}

View file

@ -7,18 +7,18 @@ use os_pipe::dup_stderr;
use pretty::pretty;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::{self, canonicalize, metadata};
use std::fs::{self, canonicalize};
use std::hash::Hasher;
use std::io::{self, BufRead, Error, ErrorKind, Read, Write};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::Arc;
use std::time::Instant;
use structopt::StructOpt;
use tokio;
use tracing::{info_span, span, Instrument};
use tracing::info_span;
mod elm;
mod fixture;
mod reporting;
mod timings;
@ -75,8 +75,9 @@ fn main() {
file,
function,
verbosity,
debug,
} => {
println!("todo transpile the code")
transpile::transpile(file, debug, function, verbosity).unwrap();
}
}
}
@ -91,41 +92,11 @@ fn exec(
) -> Result<(), Problem> {
// Our first elm make call is where we build the users program. There is a pretty good chance
// this won't work.
let mut command = Command::new("elm");
command.arg("make").arg("--output").arg("/dev/null");
//.stdin(Stdio::null());
//.stdout(Stdio::null());
// I am using the default inherit stderr behavior here.
//.stderr(Stdio::piped());
if debug {
command.arg("--debug");
}
command.arg(&file);
let span = info_span!("elm make target file");
let timing_guard = span.enter();
match command.output() {
Ok(output) => {
if !output.status.success() {
io::stderr().write_all(&output.stderr).unwrap();
io::stderr().write_all(&output.stdout).unwrap();
return Err(CompilerError::FailedBuildingFixture.into());
}
if verbosity > 0 {
io::stderr().write_all(&output.stderr).unwrap();
}
}
Err(_io_err) => {
// TODO handle this one
return Err(Problem::Wildcard("elm failed".into()));
}
}
drop(timing_guard);
elm::make(&file, debug, verbosity)?;
// Step 2, find the elm.json and elm-stuff directory
let elm_project_dir =
find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
elm::find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
@ -144,31 +115,10 @@ fn exec(
let source_checksum = hasher.finish();
// step 2.5 get the module name out of the file.
let target_module = if let Some(Ok(first_line)) = data.lines().next() {
let mut tokens = first_line.split_whitespace();
match tokens.next() {
Some(token) if token == "port" => {
tokens.next();
}
Some(token) if token == "module" => (),
Some(token) if token == "effect" => {
tokens.next();
}
_ => {
return Err(CompilerError::CantParseModule(first_line.clone()).into());
}
};
if let Some(module_name) = tokens.next() {
module_name.to_string()
} else {
return Err(CompilerError::CantParseModule(first_line.clone()).into());
}
} else {
return Err(CompilerError::EmptyModule.into());
};
let elmi::Name(target_module) = elm::parse_module_name(&data)?;
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
let interfaces = load_interfaces(&elm_cache_dir)?;
let interfaces = elm::load_interfaces(&elm_cache_dir)?;
// Step 5, check for the desired function
let span = info_span!("resolved target function");
@ -260,7 +210,7 @@ fn exec(
// `"REPLACE_ME" + "abc" * 2000` we would have over 6k bytes to write out the new code. We
// will have to do some extra book keeping to make sure the buffer space is big enough
// for the replacement code.
let mut final_script = (|| {
let final_script = (|| {
let mut final_script = data
.replace("'REPLACE_ME_WITH_JSON_STRINGIFY'", "JSON.stringify(x)")
//.replace("setTimeout", "(function(x, y) { globalThis.__bootstrap.timers.setTimeout(x, y)})")
@ -455,7 +405,7 @@ fn exec(
}
};
let start = Instant::now();
let _start = Instant::now();
let span = info_span!("create tokio runtime");
let timing_guard = span.enter();
let sys = tokio::runtime::Builder::new_current_thread()
@ -498,55 +448,6 @@ fn exec(
Ok(())
}
fn load_interfaces(elm_cache_dir: &Path) -> Result<HashMap<String, elmi::Interface>, Problem> {
let mut interfaces = HashMap::new();
let entries = fs::read_dir(&elm_cache_dir)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?;
for entry in entries {
let entry = entry.map_err(|io_err| {
CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf())
})?;
let path = entry.path();
if path.is_file() {
match path.extension() {
Some(ext) if ext == "elmi" => {
// step 4 load all the modules
let start = Instant::now();
let parse_span = info_span!("parse elmi");
let parse_guard = parse_span.enter();
let load_span = info_span!("file reads");
let load_guard = load_span.enter();
let data = std::fs::read(&path)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?;
drop(load_guard);
let start = Instant::now();
let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| {
CompilerError::FailedElmiParse("todo elmi parsing".to_owned())
})?;
if let Some(stem) = path.file_stem() {
// in theory the module name of the interface can be determined by
// the filename in elm 0.19.0 and 0.19.1
let module_name: String = stem
.to_string_lossy()
.split('-')
.collect::<Vec<&str>>()
.join(".");
interfaces.insert(module_name, i);
}
}
_ => (),
}
}
}
Ok(interfaces)
}
struct PortableHash(ahash::AHasher);
impl PortableHash {
@ -606,6 +507,8 @@ enum Arguments {
function: String,
#[structopt(short = "v", parse(from_occurrences))]
verbosity: u64,
#[structopt(long)]
debug: bool,
},
}
@ -859,7 +762,7 @@ mod runtime {
use crate::ValidatedInput;
use deno_core::error::{type_error, AnyError};
use deno_core::futures::FutureExt;
use deno_core::{resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier, OpState};
use deno_core::{resolve_url, FsModuleLoader, ModuleLoader, ModuleSpecifier};
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::permissions::Permissions;
use deno_runtime::worker::MainWorker;
@ -867,7 +770,7 @@ mod runtime {
use deno_runtime::BootstrapOptions;
use deno_web::BlobStore;
use rusty_v8 as v8;
use std::cell::RefCell;
use std::convert::TryFrom;
use std::pin::Pin;
use std::rc::Rc;
@ -922,7 +825,7 @@ mod runtime {
// let main_module = resolve_url(SPECIFIER)?;
let permissions = Permissions::allow_all();
let mut worker = MainWorker::from_options(main_module.clone(), permissions, options);
let worker = MainWorker::from_options(main_module.clone(), permissions, options);
//worker.bootstrap(&options);
Ok((worker, main_module))
@ -963,7 +866,7 @@ mod runtime {
let this = v8::undefined(scope).into();
let start = Instant::now();
let _start = Instant::now();
let span = info_span!("dispatch v8 call");
let timing_guard = span.enter();
match input {
@ -1215,37 +1118,3 @@ pub mod generated {
}
}
}
/// Look for the cargo.toml file that we are building to determine the root directory of the project
/// (so that we can locate the source files)
fn find_project_root<S: AsRef<Path>>(filename: S, search: S) -> io::Result<PathBuf> {
if !search.as_ref().is_dir() {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("`{}` is not a directory", search.as_ref().display()),
));
}
let mut path_buf = canonicalize(&search)?;
let error: io::Error = Error::new(
ErrorKind::NotFound,
format!(
"could not find `{}` in `{:?}` or any parent directory",
filename.as_ref().display(),
path_buf
),
);
loop {
let test_file = path_buf.as_path().join(&filename);
match metadata(test_file) {
Ok(_) => return Ok(path_buf),
Err(_) => (),
}
match path_buf.as_path().parent() {
Some(_) => (),
None => return Err(error),
}
path_buf.pop();
}
}

View file

@ -179,7 +179,7 @@ impl CompilerError {
impl InterpreterError {
pub fn to_doc(&self) -> Doc {
let mut title = "COMPILER ERROR";
let title = "COMPILER ERROR";
use InterpreterError::*;
let message = match self {
Setup(data_error) => Doc::text(format!("{:?}", data_error)),

View file

@ -12,9 +12,9 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::{self, ThreadId};
use std::time::{Duration, Instant};
use tracing::info_span;
use tracing::span::{Attributes, Record};
use tracing::{Event, Id, Metadata, Subscriber};
use tracing::{Event, Id, Metadata};
//
// Each step will be described with a less than 100 char string. Steps will overlap.
@ -82,7 +82,7 @@ impl ReportHandle {
}
impl tracing::Subscriber for Timings {
fn enabled(&self, metadata: &Metadata<'_>) -> bool {
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
true
}

View file

@ -1 +1,255 @@
use crate::reporting::{CompilerError, Problem, TypeError};
use elmi::DataBinary;
use std::collections::HashMap;
use std::path::{PathBuf};
use tracing::info_span;
use crate::elm;
pub fn transpile(
file: PathBuf,
debug: bool,
function: String,
//output: Option<PathBuf>,
verbosity: u64,
) -> Result<(), Problem> {
// Our first elm make call is where we build the users program. There is a pretty good chance
// this won't work.
elm::make(&file, debug, verbosity)?;
// step 2 find the elm artifacts cache directory just like with exec
let elm_project_dir =
elm::find_project_root("elm.json", "./").map_err(CompilerError::MissingElmJson)?;
let elm_cache_dir = elm_project_dir.join("elm-stuff").join("0.19.1");
if !elm_cache_dir.is_dir() {
return Err(CompilerError::MissingElmStuff(elm_cache_dir).into());
}
let data = std::fs::read(&file)
.map_err(|io_err| CompilerError::ReadInputFailed(io_err, file.clone()))?;
let elmi::Name(target_module) = elm::parse_module_name(&data)?;
// Side note: the transpile function really should be taking a list of functions in modules and
// transpiling the entire forest of dependencies. This would allow avoiding kernel javascript
// used in the project. So I would want to reduce the tuples of file function into a set of
// files to load. Then recursively load all dependencies.
// step 3 find all the filepaths in the elm-stuff/0.19.1/* folder
let interfaces = elm::load_interfaces(&elm_cache_dir)?;
// Step 4, check for the desired functions have types that we can compile.
let span = info_span!("resolved target function");
let timing_guard = span.enter();
match interfaces.get(&target_module) {
Some(interface) => match interface.values.get(&elmi::Name::from(&function)) {
Some(annotation) => {
let elmi::CannonicalAnnotation(_free_vars, tipe) = annotation;
validate_function_type(tipe)?
}
None => return Err(CompilerError::BadImport(target_module, function).into()),
},
None => return Err(CompilerError::MissingModuleTypeInformation(target_module).into()),
};
drop(timing_guard);
// all the symbols in author/project will be found in the elm_cache_dir, while the rest will be
// found in the elm_home_dir
// Step 5, recursively load all the symbols from the ~/.elm stuff artifacts.dat
println!("ok the function was acceptable, ready to build symbol table");
let objects = elm::load_objects(&elm_cache_dir)?;
let entrypoint = elmi::Global(
elmi::ModuleNameCanonical {
package: elmi::PackageName::new("author", "project"),
module: elmi::Name(target_module.clone()),
},
elmi::Name(function.clone()),
);
println!("the artifacts has the symbol {}", objects.contains_key(&entrypoint));
//let visited: HashSet<Global> = HashSet::new();
for (_key, node) in objects.iter() {
//println!("key {}", key);
match node {
elmi::Node::Define(_expr, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
elmi::Node::DefineTailFunc(_, _, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
elmi::Node::Ctor(_, _) => {
}
elmi::Node::Enum(_) => {
}
elmi::Node::Box => {
}
elmi::Node::Link(dep) => {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
elmi::Node::Cycle(_, _, _, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
elmi::Node::Manager(_) => (),
elmi::Node::Kernel(_, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
elmi::Node::PortIncoming(_, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
elmi::Node::PortOutgoing(_, deps) => {
for dep in deps.iter() {
if !objects.contains_key(&dep) {
println!("could not find dep {}", dep);
}
}
}
}
}
let _symbol_table: HashMap<elmi::Global, elmi::Node> = HashMap::new();
// step 6, start generating rust code using a tree visitor on each of the entry points.
// Accumulate the contents of each rust module in map.
//
// step 6a decide on the name mangling rules for elm indentifiers to rust identifiers. I can't
// use the $ in identifiers. Maybe I could use Z. Not sure why I can't use underscore.
// author_project__module_name__identifier?
//
// step 6b figure out how to handle partial apply, one approach requires me to track the arity
// of each function call and generate a custom closure for each partial apply.
// step 7 write out each of the generated rust modulues.
Ok(())
}
fn validate_function_type(tipe: &elmi::Type) -> Result<(), TypeError> {
match tipe {
elmi::Type::TLambda(a, b) => {
// We want to check the output types first because this is where we will figure out if
// there is more than one argument. I only want to accept functions with a single
// argument to keep the complexity down while I figure out how to map over to rust. I
// am specifically worried about implementing partial application in a performant way.
// Nested partial application worries me.
validate_output_type(&**b)?;
validate_input_type(&**a)?;
Ok(())
}
elmi::Type::TVar(_) => Err(TypeError::CantEvalGeneric),
elmi::Type::TType(module_name, name, args) if args.is_empty() => {
// If our function returns a primitive type
if module_name == "elm/core/String" && name == "String" {
return Ok(());
}
if module_name == "elm/bytes/Bytes" && name == "Bytes" {
return Ok(());
}
Err(TypeError::CantEvalType(tipe.clone()))
}
elmi::Type::TType(_, _, _) => Err(TypeError::CantEvalCustomType),
elmi::Type::TRecord(_, _) => Err(TypeError::CantEvalRecord),
elmi::Type::TUnit => Err(TypeError::CantEvalUnit),
elmi::Type::TTuple(_, _, _) => Err(TypeError::CantEvalTuple),
elmi::Type::TAlias(_, _, _, ref alias) => {
match &**alias {
elmi::AliasType::Filled(tipe) => {
// I think the recursion is limited to a single step. I have not tested what
// the CannonicalAnnotation would look like for a doubly indirect alias, for
// example for `view` below
// ```elm
// type alias Foo = Int
// type alias Bar = String
//
// type alias Zap = Foo -> Bar
//
// view : Zap
// ```
validate_function_type(tipe)
}
elmi::AliasType::Holey(_) => return Err(TypeError::CantEvalHoleyAlias),
}
}
}
}
fn validate_input_type(tipe: &elmi::Type) -> Result<(), TypeError> {
match tipe {
elmi::Type::TLambda(_, _) => Err(TypeError::EvalRequiresSingleArgument(tipe.clone())),
elmi::Type::TType(module_name, name, args) if args.is_empty() => {
if module_name == "elm/core/String" && name == "String" {
Ok(())
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
Ok(())
} else if module_name == "elm/json/Json.Encode" && name == "Value" {
Ok(())
} else {
Err(TypeError::InputTypeNotSupported(tipe.clone()))
}
}
elmi::Type::TAlias(_, _, _, ref alias) => match &**alias {
elmi::AliasType::Filled(tipe) => validate_input_type(tipe),
elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias),
},
_ => Err(TypeError::OutputTypeNotSupported(tipe.clone())),
}
}
fn validate_output_type(tipe: &elmi::Type) -> Result<(), TypeError> {
match tipe {
elmi::Type::TType(module_name, name, _args) => {
if module_name == "elm/core/String" && name == "String" {
Ok(())
} else if module_name == "elm/bytes/Bytes" && name == "Bytes" {
Ok(())
} else if module_name == "elm/json/Json.Encode" && name == "Value" {
Ok(())
} else if module_name == "elm/virtual-dom/VirtualDom" && name == "Node" {
Ok(())
} else {
Err(TypeError::OutputTypeNotSupported(tipe.clone()))
}
}
elmi::Type::TAlias(_, _, _, ref alias) => match &**alias {
elmi::AliasType::Filled(tipe) => validate_output_type(tipe),
elmi::AliasType::Holey(_) => Err(TypeError::CantEvalHoleyAlias),
},
_ => Err(TypeError::OutputTypeNotSupported(tipe.clone())),
}
}
// Figure out how to do structural types. If I could name mangle all the functions I could write
// them out in the same namespace as lambdas which would avoid the structural typing problem if the
// lambda was used by one type. Monomorphism. But if the lambda is used by multiple types then I
// would either need to narrow the type into a tuple at the call site, generate a specialize struct
// and borrow all the children.
//
//
// The question is should I use Rc with immutable datastructures? Or should I try to statically
// analyse copies and use mutable state when possible.