From 93d9d3e29d0b61493c6c6241419850ab145701f8 Mon Sep 17 00:00:00 2001 From: cosine Date: Mon, 28 Mar 2022 14:12:19 -0400 Subject: [PATCH] [WIP] Add first-class method calls; put in table --- libjankscripten/src/jankyscript/methods.rs | 65 +++++++++++++++++++ libjankscripten/src/jankyscript/mod.rs | 2 +- libjankscripten/src/jankyscript/prototypes.rs | 57 ---------------- libjankscripten/src/jankyscript/syntax.rs | 1 + .../src/jankyscript/type_checking.rs | 17 ++--- libjankscripten/src/jankyscript/typeinf.rs | 16 +---- libjankscripten/src/javascript/desugar.rs | 1 + libjankscripten/src/javascript/mod.rs | 1 + .../src/javascript/resugar_method_call.rs | 34 ++++++++++ libjankscripten/src/javascript/syntax.rs | 1 + libjankscripten/src/notwasm/syntax.rs | 1 + libjankscripten/src/rts_function.rs | 13 ++++ 12 files changed, 125 insertions(+), 84 deletions(-) create mode 100644 libjankscripten/src/jankyscript/methods.rs delete mode 100644 libjankscripten/src/jankyscript/prototypes.rs create mode 100644 libjankscripten/src/javascript/resugar_method_call.rs diff --git a/libjankscripten/src/jankyscript/methods.rs b/libjankscripten/src/jankyscript/methods.rs new file mode 100644 index 0000000..574098f --- /dev/null +++ b/libjankscripten/src/jankyscript/methods.rs @@ -0,0 +1,65 @@ +use crate::{shared::Type, typ}; +use lazy_static::lazy_static; +use std::collections::HashMap; + +macro_rules! entry { + ($name:ident, $($args:tt -> $ret:ident),+) => { + (stringify!($name), vec![$(typ!(fun $args -> $ret)),+]) + }; +} + +fn methods_table() -> HashMap<&'static str, Vec> { + [ + entry!(slice, (string, int, int) -> string, (array, int, int) -> array), + entry!(at, (string, int) -> any, (string, int) -> string), + entry!(concat, (array, array) -> array, (string, string) -> string), + // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array + // Array.prototype[@@unscopables] // ?? + // length is actually special and not really part of the prototype but + // for the purpose of type inference it should be here + //"length", + // Array.prototype[@@iterator]() // ?? + // get Array[@@species] // ?? + //"copyWithin", + //"entries", + //"every", + //"fill", + //"filter", + //"find", + //"findIndex", + //"flat", + //"flatMap", + //"forEach", + //"from", // Array.from, not prototype + //"includes", + //"indexOf", + //"isArray", // Array.isArray + //"join", + //"keys", + //"lastIndexOf", + //"map", + //"of", // Array.of + //"pop", + //"push", + //"reduce", + //"reduceRight", + //"reverse", + //"shift", + //"slice", + //"some", + //"sort", + //"splice", + //"toLocaleString", + //"toSource", + //"toString", + //"unshift", + //"values", + ] + .iter() + .cloned() + .collect() +} + +lazy_static! { + pub static ref METHODS_TABLE: HashMap<&'static str, Vec> = methods_table(); +} diff --git a/libjankscripten/src/jankyscript/mod.rs b/libjankscripten/src/jankyscript/mod.rs index e804622..8020562 100644 --- a/libjankscripten/src/jankyscript/mod.rs +++ b/libjankscripten/src/jankyscript/mod.rs @@ -7,10 +7,10 @@ pub mod constructors; pub mod from_js; mod fv; mod insert_returns; +mod methods; mod operators; mod operators_z3; mod pretty; -mod prototypes; pub mod syntax; mod type_checking; mod typeinf; diff --git a/libjankscripten/src/jankyscript/prototypes.rs b/libjankscripten/src/jankyscript/prototypes.rs deleted file mode 100644 index f05feaa..0000000 --- a/libjankscripten/src/jankyscript/prototypes.rs +++ /dev/null @@ -1,57 +0,0 @@ -use lazy_static::lazy_static; -use std::collections::HashSet; - -fn array_prototype() -> HashSet<&'static str> { - [ - // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array - // Array.prototype[@@unscopables] // ?? - // length is actually special and not really part of the prototype but - // for the purpose of type inference it should be here - "length", - // Array.prototype[@@iterator]() // ?? - // get Array[@@species] // ?? - "at", - "concat", - "copyWithin", - "entries", - "every", - "fill", - "filter", - "find", - "findIndex", - "flat", - "flatMap", - "forEach", - "from", // Array.from, not prototype - "includes", - "indexOf", - "isArray", // Array.isArray - "join", - "keys", - "lastIndexOf", - "map", - "of", // Array.of - "pop", - "push", - "reduce", - "reduceRight", - "reverse", - "shift", - "slice", - "some", - "sort", - "splice", - "toLocaleString", - "toSource", - "toString", - "unshift", - "values", - ] - .iter() - .cloned() - .collect() -} - -lazy_static! { - pub static ref ARRAY_PROTOTYPE: HashSet<&'static str> = array_prototype(); -} diff --git a/libjankscripten/src/jankyscript/syntax.rs b/libjankscripten/src/jankyscript/syntax.rs index 511003f..4b96fe7 100644 --- a/libjankscripten/src/jankyscript/syntax.rs +++ b/libjankscripten/src/jankyscript/syntax.rs @@ -131,6 +131,7 @@ pub enum Expr { Binary(BinaryOp, Box, Box, Pos), Assign(Box, Box, Pos), Call(Box, Vec, Pos), + MethodCall(Id, Vec, Type, Pos), PrimCall(RTSFunction, Vec, Pos), Func(Func, Pos), Closure(Func, Vec<(Expr, Type)>, Pos), diff --git a/libjankscripten/src/jankyscript/type_checking.rs b/libjankscripten/src/jankyscript/type_checking.rs index e48a0a2..fdd3ced 100644 --- a/libjankscripten/src/jankyscript/type_checking.rs +++ b/libjankscripten/src/jankyscript/type_checking.rs @@ -2,7 +2,6 @@ //! This occurs after type inference to ensure that type inference succeeded //! correctly. -use super::prototypes; use super::syntax::*; use crate::pos::Pos; use crate::shared::std_lib::get_global_object; @@ -357,6 +356,10 @@ fn type_check_expr(expr: &Expr, env: Env) -> TypeCheckingResult { // type check this call type_check_fun_call(fun_type, args, env, s.clone()) } + Expr::MethodCall(obj, args, typ, s) => { + let obj_type = lookup(&env, &obj, &s)?; + type_check_fun_call(typ.clone(), args, env, s.clone()) + } Expr::Coercion(coercion, e, s) => { // type the expression. regardless of the coercion, the expression // needs to be well-typed. @@ -397,18 +400,6 @@ fn type_check_expr(expr: &Expr, env: Env) -> TypeCheckingResult { Expr::Dot(obj, prop, s) => { let obj_type = type_check_expr(obj, env)?; - // TODO(luna): , Date, String, ... - if !(prototypes::ARRAY_PROTOTYPE.contains(prop.name()) - && (obj_type == Type::Array || obj_type == Type::Any)) - { - ensure( - "property lookup expects a DynObject", - Type::DynObject, - obj_type, - &s, - )?; - } - // we don't know anything about the type we're returning. // even if this property doesn't exist on the given object, // it'll return undefined (in non-strict mode). diff --git a/libjankscripten/src/jankyscript/typeinf.rs b/libjankscripten/src/jankyscript/typeinf.rs index b9c6d97..11593cb 100644 --- a/libjankscripten/src/jankyscript/typeinf.rs +++ b/libjankscripten/src/jankyscript/typeinf.rs @@ -19,7 +19,6 @@ use super::super::shared::coercions::Coercion; use super::operators::OVERLOADS; use super::operators_z3::Z3Operators; -use super::prototypes; use super::syntax::*; use super::typeinf_env::Env; use super::walk::{Loc, Visitor}; @@ -365,21 +364,12 @@ impl<'a> Typeinf<'a> { fn cgen_dot(&mut self, obj_e: &mut Expr, x: &mut Id, p: &mut Pos) -> ast::Bool<'a> { let w = self.fresh_weight(); let (phi_1, t) = self.cgen_expr(obj_e); - let phi_array = if prototypes::ARRAY_PROTOTYPE.contains(x.name()) { - z3f!(self, (and (= (tid t) (typ array)))) - } else { - // Since the field is part of no known prototype (we assume - // no platypus objects) (still TODO Date, String, etc), - // we know it's a DynObject, so can safely perform the - // coercion - let e = obj_e.take(); - *obj_e = coerce(t.clone(), Type::DynObject, e, p.clone()); - z3f!(self, false) - }; + let e = obj_e.take(); + *obj_e = coerce(t.clone(), Type::DynObject, e, p.clone()); let phi_2 = z3f!(self, (or (and (= (tid t) (typ dynobject)) (id w.clone())) - (and (id phi_array) (id w.clone())) + (id w.clone()) (and (= (tid t) (typ any)) (not (id &w))))); phi_1 & phi_2 } diff --git a/libjankscripten/src/javascript/desugar.rs b/libjankscripten/src/javascript/desugar.rs index 2f73d92..16bbd3f 100644 --- a/libjankscripten/src/javascript/desugar.rs +++ b/libjankscripten/src/javascript/desugar.rs @@ -24,6 +24,7 @@ pub fn desugar(stmt: &mut Stmt, ng: &mut NameGen) { // dep: desugar_function_applications, add_blocks desugar_updates::desugar_updates(stmt, ng); desugar_bracket_str::desugar_bracket_str(stmt); + resugar_method_call::resugar_method_call(stmt); } #[cfg(test)] diff --git a/libjankscripten/src/javascript/mod.rs b/libjankscripten/src/javascript/mod.rs index a9726ad..c93d30a 100644 --- a/libjankscripten/src/javascript/mod.rs +++ b/libjankscripten/src/javascript/mod.rs @@ -16,6 +16,7 @@ mod desugar_vardecls; mod lift_vars; mod normalize_std_lib_calls; mod parser; +mod resugar_method_call; pub mod syntax; pub mod walk; diff --git a/libjankscripten/src/javascript/resugar_method_call.rs b/libjankscripten/src/javascript/resugar_method_call.rs new file mode 100644 index 0000000..493aa82 --- /dev/null +++ b/libjankscripten/src/javascript/resugar_method_call.rs @@ -0,0 +1,34 @@ +use super::syntax::*; +use super::*; + +struct ResugarMethodCall; + +impl Visitor for ResugarMethodCall { + fn exit_expr(&mut self, expr: &mut Expr, _: &Loc) { + match expr { + Expr::Call(f, args, _) => match &mut **f { + Expr::Dot(obj, name, s) => { + let id = if let Expr::Id(obj_name, _) = &**obj { + obj_name.clone() + } else { + panic!("Desugar should have named method objects") + }; + let name = if let Id::Named(field) = name { + std::mem::replace(field, Default::default()) + } else { + panic!("Dot should't access special ids") + }; + let args = std::mem::replace(args, vec![]); + *expr = Expr::MethodCall(id, name, args, s.clone()); + } + _ => (), + }, + _ => (), + } + } +} + +pub fn resugar_method_call(program: &mut Stmt) { + let mut v = ResugarMethodCall; + program.walk(&mut v); +} diff --git a/libjankscripten/src/javascript/syntax.rs b/libjankscripten/src/javascript/syntax.rs index bf738c7..f402429 100644 --- a/libjankscripten/src/javascript/syntax.rs +++ b/libjankscripten/src/javascript/syntax.rs @@ -132,6 +132,7 @@ pub enum Expr { If(Box, Box, Box, Pos), Assign(AssignOp, Box, Box, Pos), Call(Box, Vec, Pos), + MethodCall(Id, String, Vec, Pos), Func(Option, Vec, Box, Pos), Seq(Vec, Pos), } diff --git a/libjankscripten/src/notwasm/syntax.rs b/libjankscripten/src/notwasm/syntax.rs index 252ec0e..ec5826b 100644 --- a/libjankscripten/src/notwasm/syntax.rs +++ b/libjankscripten/src/notwasm/syntax.rs @@ -304,6 +304,7 @@ pub enum Expr { /// right now, never constructed from jankyscript, only in tests Call(Id, Vec, Pos), ClosureCall(Id, Vec, Pos), + AnyMethodCall(Id, Vec, Vec, Pos), PrimCall(RTSFunction, Vec, Pos), ObjectEmpty, /// `ObjectSet(obj, field, value, _)` is `obj.field = value;`. The translator generates code diff --git a/libjankscripten/src/rts_function.rs b/libjankscripten/src/rts_function.rs index f237ce8..787e0bf 100644 --- a/libjankscripten/src/rts_function.rs +++ b/libjankscripten/src/rts_function.rs @@ -11,6 +11,10 @@ use strum_macros::EnumIter; #[derive(Debug, Clone, PartialEq, EnumIter, Eq, Hash)] pub enum RTSFunction { Todo(&'static str), + // Type-specialized methods. They are always implemented in rust, with a + // name given algorithmically based on the type and method name. The full type is + // also provided, because it's generated in the methods table + Method(&'static str, Type), // unary ops Typeof, Delete, @@ -52,6 +56,7 @@ impl RTSFunction { use RTSFunctionImpl::*; match self { Todo(name) => todo!("unimplemented operator: {}", name), + Method(..) => Rust(self.to_string()), Typeof => Rust("janky_typeof".into()), Delete => Rust("janky_delete".into()), Void => Rust("janky_void".into()), @@ -101,6 +106,7 @@ impl RTSFunction { use RTSFunction::*; match self { Todo(name) => todo!("unimplemented operator: {}", name), + Method(_, ty) => ty.clone(), Typeof => Function(vec![Any], Box::new(String)), // the second operand of InstanceOf is really "a function" but we don't have a type for that Delete | InstanceOf => Function(vec![Any, Any], Box::new(Bool)), @@ -127,6 +133,13 @@ impl std::fmt::Display for RTSFunction { "{}", match self { Todo(s) => s, + Method(name, ty) => { + if let Type::Function(ts, _) = ty { + return write!(f, "{}_{}", ts[0], name); + } else { + panic!("non-function function type") + } + } Typeof => "typeof", Delete => "delete", Void => "void",