Merge remote-tracking branch 'refs/remotes/origin/simplifier'

This commit is contained in:
강동윤 2018-01-14 13:47:34 +09:00
commit 43fcbcbef2
12 changed files with 1253 additions and 0 deletions

View File

@ -66,6 +66,18 @@ fn main() {
"private",
"protected",
"public",
// Used by transforms.
"Array",
"Object",
"Infinity",
"NaN",
"function",
"string",
"number",
"boolean",
"object",
"undefined",
"length",
],
);
}

View File

@ -6,6 +6,7 @@ authors = ["강동윤 <kdy1@outlook.kr>"]
[dependencies]
swc_ecma_ast = { path = "./ast" }
swc_ecma_parser = { path = "./parser" }
swc_ecma_transforms = { path = "./transforms" }
[dev-dependencies]

View File

@ -1,2 +1,3 @@
pub extern crate swc_ecma_ast as ast;
pub extern crate swc_ecma_parser as parser;
pub extern crate swc_ecma_transforms as transforms;

View File

@ -0,0 +1,13 @@
[package]
name = "swc_ecma_transforms"
version = "0.1.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
[dependencies]
swc_atoms = { path = "../../atoms" }
swc_common = { path = "../../common" }
swc_ecma_ast = { path = "../ast" }
[dev-dependencies]
swc_ecma_parser = { path = "../parser" }
testing = { path = "../../testing" }

View File

@ -0,0 +1,14 @@
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(specialization)]
#[macro_use]
pub extern crate swc_atoms;
pub extern crate swc_common;
#[macro_use]
pub extern crate swc_ecma_ast as ast;
#[macro_use]
mod macros;
pub mod simplify;
pub mod util;

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,191 @@
use super::Simplify;
use ast::*;
use ast::{Ident, Lit};
use ast::ExprKind::*;
use swc_common::fold::{FoldWith, Folder};
use util::*;
impl Folder<ExprKind> for Simplify {
fn fold(&mut self, expr: ExprKind) -> ExprKind {
// fold children nodes.
let expr = expr.fold_children(self);
match expr {
// Do nothing for literals.
Lit(_) => expr,
Unary { prefix, op, arg } => fold_unary(prefix, op, arg),
Binary { left, op, right } => fold_bin(left, op, right),
Cond { test, cons, alt } => match test.as_bool() {
(p, Known(val)) => {
let expr_value = if val { cons } else { alt };
if p.is_pure() {
expr_value.node
} else {
Seq {
exprs: vec![test, expr_value],
}
}
}
_ => Cond { test, cons, alt },
},
// Simplify sequence expression.
Seq { exprs } => if exprs.len() == 1 {
exprs.into_iter().next().unwrap().node
} else {
assert!(!exprs.is_empty(), "sequence expression should not be empty");
//TODO: remove unused
return Seq { exprs };
},
// be conservative.
_ => expr,
}
}
}
fn fold_bin(left: Box<Expr>, op: BinaryOp, right: Box<Expr>) -> ExprKind {
let (left, right) = match op {
op!(bin "+") => return fold_add(left, right),
op!("&&") | op!("||") => match left.as_bool() {
(Pure, Known(val)) => {
if op == op!("&&") {
if val {
// 1 && $right
return right.node;
} else {
// 0 && $right
return Lit(Lit::Bool(false));
}
} else {
if val {
// 1 || $right
return left.node;
} else {
// 0 || $right
return right.node;
}
}
}
_ => (left, right),
},
op!("instanceof") => (left, right),
op!(bin "-") | op!("/") | op!("%") => {
// Arithmetic operations
(left, right)
}
_ => (left, right),
};
Binary { left, op, right }
}
///See https://tc39.github.io/ecma262/#sec-addition-operator-plus
fn fold_add(left: Box<Expr>, right: Box<Expr>) -> ExprKind {
// It's string concatenation if either left or right is string.
Binary {
left,
op: op!(bin "+"),
right,
}
}
fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
let span = arg.span;
match op {
op!("typeof") => {
let val = match arg.node {
Function(..) => "function",
Lit(Lit::Str(..)) => "string",
Lit(Lit::Num(..)) => "number",
Lit(Lit::Bool(..)) => "boolean",
Lit(Lit::Null) | Object { .. } | Array { .. } => "object",
Unary {
prefix: true,
op: op!("void"),
..
}
| Ident(Ident {
sym: js_word!("undefined"),
..
}) => {
// We can assume `undefined` is `undefined`,
// because overriding `undefined` is always hard error in swc.
"undefined"
}
_ => {
return Unary {
prefix: true,
op: op!("typeof"),
arg,
}
}
};
return Lit(Lit::Str(val.into()));
}
op!("!") => match arg.as_bool() {
(p, Known(val)) => {
let new = Lit(Lit::Bool(!val));
return if p.is_pure() {
new
} else {
Seq {
exprs: vec![arg, box Expr { span, node: new }],
}
};
}
_ => {
return Unary {
op,
arg,
prefix: true,
}
}
},
op!(unary "+") => {}
op!(unary "-") => match arg.node {
Ident(Ident {
sym: js_word!("Infinity"),
..
}) => return Unary { prefix, op, arg },
// "-NaN" is "NaN"
Ident(Ident {
sym: js_word!("NaN"),
..
}) => return arg.node,
Lit(Lit::Num(Number(f))) => return Lit(Lit::Num(Number(-f))),
_ => {
// TODO: Report that user is something bad (negating non-number value)
}
},
_ => {}
}
Unary { prefix, op, arg }
}
/// Try to fold arithmetic binary operators
fn perform_arithmetic_op(op: BinaryOp, left: Box<Expr>, right: Box<Expr>) -> ExprKind {
let (lv, rv) = (left.as_number(), right.as_number());
if lv.is_unknown() && rv.is_unknown() {
return Binary { left, op, right };
}
Binary { left, op, right }
}
/// https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
fn perform_abstract_eq_cmp(left: &Expr, right: &Expr) -> Value<bool> {}
/// https://tc39.github.io/ecma262/#sec-strict-equality-comparison
fn perform_strict_eq_cmp(left: &Expr, right: &Expr) -> Value<bool> {}

View File

@ -0,0 +1,186 @@
//! Ported from closure compiler.
use ast::*;
use swc_common::fold::{FoldWith, Folder};
use util::*;
mod expr;
#[derive(Debug, Clone, Copy)]
pub struct Simplify;
impl<T: StmtLike> Folder<Vec<T>> for Simplify
where
Self: Folder<T>,
{
fn fold(&mut self, stmts: Vec<T>) -> Vec<T> {
let mut buf = Vec::with_capacity(stmts.len());
for stmt_like in stmts {
let stmt_like = self.fold(stmt_like);
let stmt_like = match stmt_like.try_into_stmt() {
Ok(stmt) => {
let span = stmt.span;
let stmt = match stmt.node {
// Remove empty statements.
StmtKind::Empty => continue,
StmtKind::Throw { .. }
| StmtKind::Return { .. }
| StmtKind::Continue { .. }
| StmtKind::Break { .. } => {
let stmt_like = T::from_stmt(stmt);
buf.push(stmt_like);
return buf;
}
// Optimize if statement.
StmtKind::If {
test,
consequent,
alt,
} => {
// check if
let node = match test.as_bool() {
(Pure, Known(val)) => {
if val {
consequent.node
} else {
alt.map(|alt| alt.node).unwrap_or_else(|| StmtKind::Empty)
}
}
// TODO: Impure
_ => StmtKind::If {
test,
consequent,
alt,
},
};
Stmt { span, node }
}
_ => stmt,
};
T::from_stmt(stmt)
}
Err(stmt_like) => stmt_like,
};
buf.push(stmt_like);
}
buf
}
}
impl Folder<StmtKind> for Simplify {
fn fold(&mut self, stmt: StmtKind) -> StmtKind {
let stmt = stmt.fold_children(self);
match stmt {
// `1;` -> `;`
StmtKind::Expr(box Expr { span, node }) => match node {
ExprKind::Lit(Lit::Num(..))
| ExprKind::Lit(Lit::Bool(..))
| ExprKind::Lit(Lit::Regex(..)) => StmtKind::Empty,
//
// Function expressions are useless if they are not used.
//
// As function expressions cannot start with 'function',
// this will be reached only if other things
// are removed while folding chilren.
ExprKind::Function(..) => StmtKind::Empty,
_ => StmtKind::Expr(box Expr { node, span }),
},
StmtKind::Block(BlockStmt { span, stmts }) => {
if stmts.len() == 0 {
return StmtKind::Empty;
} else if stmts.len() == 1 {
// TODO: Check if lexical variable exists.
return stmts.into_iter().next().unwrap().node;
} else {
StmtKind::Block(BlockStmt { span, stmts })
}
}
StmtKind::Try {
block,
handler,
finalizer,
} => {
// Only leave the finally block if try block is empty
if block.is_empty() {
return finalizer.map(StmtKind::Block).unwrap_or(StmtKind::Empty);
}
// If catch block and finally block is empty, remove try-catch is useless.
if handler.is_empty() && finalizer.is_empty() {
return StmtKind::Block(block);
}
StmtKind::Try {
block,
handler,
finalizer,
}
}
// Remove empty else block.
// As we fold children before parent, unused expression
// statements without side effects are converted to
// StmtKind::Empty before here.
StmtKind::If {
test,
consequent,
alt,
} => {
if alt.is_empty() {
return StmtKind::If {
test,
consequent,
alt: None,
};
}
StmtKind::If {
test,
consequent,
alt,
}
}
_ => stmt,
}
}
}
pub trait StmtLike: Sized {
fn try_into_stmt(self) -> Result<Stmt, Self>;
fn from_stmt(stmt: Stmt) -> Self;
}
impl StmtLike for Stmt {
fn try_into_stmt(self) -> Result<Stmt, Self> {
Ok(self)
}
fn from_stmt(stmt: Stmt) -> Self {
stmt
}
}
impl StmtLike for ModuleItem {
fn try_into_stmt(self) -> Result<Stmt, Self> {
match self {
ModuleItem::Stmt(stmt) => Ok(stmt),
_ => Err(self),
}
}
fn from_stmt(stmt: Stmt) -> Self {
ModuleItem::Stmt(stmt)
}
}
// impl Folder<Stmt> for Simplify {
// fn fold(&mut self, stmt: Stmt) -> Stmt {
// stmt.fold_children(&mut FoldConst)
// }
// }

View File

@ -0,0 +1,407 @@
pub use self::Purity::{MayBeImpure, Pure};
pub use self::value::Type::{self, Bool as BoolType, Null as NullType, Num as NumberType,
Obj as ObjectType, Str as StringType, Symbol as SymbolType,
Undefined as UndefinedType};
pub use self::value::Value::{self, Known, Unknown};
use ast::*;
use std::borrow::Cow;
use std::f64::{INFINITY, NAN};
use std::num::FpCategory;
use std::ops::Add;
mod value;
pub type Bool = Value<bool>;
pub trait IsEmpty {
fn is_empty(&self) -> bool;
}
impl IsEmpty for BlockStmt {
fn is_empty(&self) -> bool {
self.stmts.is_empty()
}
}
impl IsEmpty for CatchClause {
fn is_empty(&self) -> bool {
self.body.stmts.is_empty()
}
}
impl IsEmpty for StmtKind {
fn is_empty(&self) -> bool {
match *self {
StmtKind::Empty => true,
StmtKind::Block(ref b) => b.is_empty(),
_ => false,
}
}
}
impl IsEmpty for Stmt {
fn is_empty(&self) -> bool {
self.node.is_empty()
}
}
impl<T: IsEmpty> IsEmpty for Option<T> {
fn is_empty(&self) -> bool {
match *self {
Some(ref node) => node.is_empty(),
None => true,
}
}
}
impl<T: IsEmpty> IsEmpty for Box<T> {
fn is_empty(&self) -> bool {
<T as IsEmpty>::is_empty(&*self)
}
}
pub trait ExprExt: Sized + AsRef<Expr> {
///
/// This method emulates the `Boolean()` JavaScript cast function.
///Note: unlike getPureBooleanValue this function does not return `None`
///for expressions with side-effects.
fn as_bool(&self) -> (Purity, Bool) {
let expr = self.as_ref();
let val = match expr.node {
ExprKind::Paren(ref e) => return e.as_bool(),
ExprKind::Seq { ref exprs } => return exprs.last().unwrap().as_bool(),
ExprKind::Assign { ref right, .. } => return right.as_bool(),
ExprKind::Unary {
prefix: true,
op: op!("!"),
ref arg,
} => {
let (p, v) = arg.as_bool();
return (p, !v);
}
ExprKind::Binary {
ref left,
op: op @ op!("&"),
ref right,
}
| ExprKind::Binary {
ref left,
op: op @ op!("|"),
ref right,
} => {
// TODO: Ignore purity if value cannot be reached.
let (lp, lv) = left.as_bool();
let (rp, rv) = right.as_bool();
if lp + rp == Pure {
return (Pure, lv.and(rv));
}
if op == op!("&") {
lv.and(rv)
} else {
lv.or(rv)
}
}
ExprKind::Function(..)
| ExprKind::Class(..)
| ExprKind::New { .. }
| ExprKind::Array { .. }
| ExprKind::Object { .. } => Known(true),
ExprKind::Unary {
prefix: true,
op: op!("void"),
arg: _,
} => Known(false),
ExprKind::Lit(ref lit) => {
return (
Pure,
Known(match *lit {
Lit::Num(Number(n)) => match n.classify() {
FpCategory::Nan | FpCategory::Zero => false,
_ => true,
},
Lit::Bool(b) => b,
Lit::Str(ref s) => !s.is_empty(),
Lit::Null => false,
Lit::Regex(..) => true,
}),
)
}
//TODO?
_ => Unknown,
};
(MayBeImpure, val)
}
/// Emulates javascript Number() cast function.
fn as_number(&self) -> Value<f64> {
let expr = self.as_ref();
let v = match expr.node {
ExprKind::Lit(ref l) => match *l {
Lit::Bool(true) => 1.0,
Lit::Bool(false) | Lit::Null => 0.0,
Lit::Num(Number(n)) => n,
Lit::Str(ref s) => return num_from_str(s),
_ => return Unknown,
},
ExprKind::Ident(Ident { ref sym, .. }) => match &**sym {
"undefined" | "NaN" => NAN,
"Infinity" => INFINITY,
_ => return Unknown,
},
ExprKind::Unary {
prefix: true,
op: op!(unary "-"),
arg:
box Expr {
span: _,
node:
ExprKind::Ident(Ident {
sym: js_word!("Infinity"),
..
}),
},
} => -INFINITY,
ExprKind::Unary {
prefix: true,
op: op!("!"),
ref arg,
} => match arg.as_bool() {
(Pure, Known(v)) => {
if v {
0.0
} else {
1.0
}
}
_ => return Unknown,
},
ExprKind::Unary {
prefix: true,
op: op!("void"),
arg: _,
} => {
// if arg.may_have_side_effects() {
return Unknown;
// } else {
// NAN
// }
}
ExprKind::Tpl(..) | ExprKind::Object { .. } | ExprKind::Array { .. } => {
match self.as_string() {
Some(ref s) => return num_from_str(s),
None => return Unknown,
}
}
_ => return Unknown,
};
Known(v)
}
fn as_string(&self) -> Option<Cow<str>> {
let expr = self.as_ref();
match expr.node {
ExprKind::Lit(ref l) => match *l {
Lit::Str(ref s) => Some(Cow::Borrowed(s)),
Lit::Num(ref n) => Some(format!("{}", n).into()),
Lit::Bool(true) => Some(Cow::Borrowed("true")),
Lit::Bool(false) => Some(Cow::Borrowed("false")),
Lit::Null => Some(Cow::Borrowed("null")),
_ => None,
},
ExprKind::Tpl(_) => {
// TODO:
// Only convert a template literal if all its expressions can be converted.
unimplemented!("TplLit.as_string()")
}
ExprKind::Ident(Ident { ref sym, .. }) => match &**sym {
"undefined" | "Infinity" | "NaN" => Some(Cow::Borrowed(&**sym)),
_ => None,
},
ExprKind::Unary {
prefix: true,
op: op!("void"),
..
} => Some(Cow::Borrowed("undefined")),
ExprKind::Unary {
prefix: true,
op: op!("!"),
ref arg,
} => match arg.as_bool() {
(Pure, Known(v)) => Some(Cow::Borrowed(if v { "false" } else { "true" })),
_ => None,
},
ExprKind::Array { ref elems } => {
let mut first = true;
let mut buf = String::new();
// null, undefined is "" in array literl.
for elem in elems {
let e = match *elem {
Some(ref elem) => match *elem {
ExprOrSpread::Expr(ref e) | ExprOrSpread::Spread(ref e) => match e.node
{
ExprKind::Lit(Lit::Null)
| ExprKind::Ident(Ident {
sym: js_word!("undefined"),
..
}) => Cow::Borrowed(""),
_ => match e.as_string() {
Some(s) => s,
None => return None,
},
},
},
None => Cow::Borrowed(""),
};
buf.push_str(&e);
if first {
first = false;
} else {
buf.push(',');
}
}
Some(buf.into())
}
ExprKind::Object { .. } => Some(Cow::Borrowed("[object Object]")),
_ => None,
}
}
fn get_type(&self) -> Value<Type> {
let expr = self.as_ref();
match expr.node {
ExprKind::Assign { ref right, .. } => right.get_type(),
ExprKind::Seq { ref exprs } => exprs
.last()
.expect("sequence expression should not be empty")
.get_type(),
ExprKind::Binary {
ref left,
op: op!("&&"),
ref right,
}
| ExprKind::Binary {
ref left,
op: op!("||"),
ref right,
} => {}
ExprKind::Assign {
ref left,
op: AssignOp::AddAssign,
ref right,
} => {
if right.get_type() == Known(StringType) {
return Known(StringType);
}
return Unknown;
}
ExprKind::Ident(Ident { ref sym, .. }) => {
return Known(match *sym {
js_word!("undefined") => UndefinedType,
js_word!("NaN") | js_word!("Infinity") => NumberType,
_ => return Unknown,
})
}
ExprKind::Lit(Lit::Num(..))|
ExprKind::Assign{op:op!("&="),..}|
ExprKind::Assign{op:op!("^="),..}|
ExprKind::Assign{op:op!("|="),..}|
ExprKind::Assign{op:op!("<<="),..}|
ExprKind::Assign{op:op!(">>="),..}|
ExprKind::Assign{op:op!(">>>="),..}|
ExprKind::Assign{op:op!("-="),..}|
ExprKind::Assign{op:op!("*="),..}|
ExprKind::Assign{op:op!("**="),..}|
ExprKind::Assign{op:op!("/="),..}|
ExprKind::Assign{op:op!("%="),..}
// case BITNOT:
// case BITOR:
// case BITXOR:
// case BITAND:
// case LSH:
// case RSH:
// case URSH:
// case SUB:
// case MUL:
// case MOD:
// case DIV:
// case EXPONENT:
// case INC:
// case DEC:
// case POS:
// case NEG:
=> return Known(NumberType),
}
}
}
fn num_from_str(s: &str) -> Value<f64> {
if s.contains('\u{000b}') {
return Unknown;
}
// TODO: Check if this is correct
let s = s.trim();
if s.is_empty() {
return Known(0.0);
}
if s.starts_with("0x") || s.starts_with("0X") {
return match s[2..4].parse() {
Ok(n) => Known(n),
Err(_) => Known(NAN),
};
}
if (s.starts_with('-') || s.starts_with('+'))
&& (s[1..].starts_with("0x") || s[1..].starts_with("0X"))
{
// hex numbers with explicit signs vary between browsers.
return Unknown;
}
// Firefox and IE treat the "Infinity" differently. Firefox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
match s {
"infinity" | "+infinity" | "-infinity" => return Unknown,
_ => {}
}
Known(s.parse().ok().unwrap_or(NAN))
}
impl<T: AsRef<Expr>> ExprExt for T {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Purity {
MayBeImpure,
Pure,
}
impl Purity {
pub fn is_pure(self) -> bool {
self == Pure
}
}
impl Add for Purity {
type Output = Self;
fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Pure, Pure) => Pure,
_ => MayBeImpure,
}
}
}

View File

@ -0,0 +1,63 @@
use self::Value::{Known, Unknown};
use std::ops::Not;
/// Runtime value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Value<T> {
Known(T),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Type {
Undefined,
Null,
Bool,
Str,
Symbol,
Num,
Obj,
}
impl<T> Value<T> {
pub fn is_unknown(&self) -> bool {
match *self {
Unknown => true,
_ => false,
}
}
}
impl Value<bool> {
pub fn and(self, other: Self) -> Self {
match self {
Known(true) => other,
Known(false) => Known(false),
Unknown => match other {
Known(false) => Known(false),
_ => Unknown,
},
}
}
pub fn or(self, other: Self) -> Self {
match self {
Known(true) => Known(true),
Known(false) => other,
Unknown => match other {
Known(true) => Known(true),
_ => Unknown,
},
}
}
}
impl Not for Value<bool> {
type Output = Self;
fn not(self) -> Self {
match self {
Value::Known(b) => Value::Known(!b),
Value::Unknown => Value::Unknown,
}
}
}

View File

@ -0,0 +1,302 @@
extern crate swc_common;
extern crate swc_ecma_ast as ast;
extern crate swc_ecma_parser;
extern crate swc_ecma_transforms as transforms;
#[macro_use]
extern crate testing;
use swc_common::fold::Folder;
use swc_ecma_parser::lexer::Lexer;
use swc_ecma_parser::parser::Parser;
use testing::logger;
use transforms::simplify::Simplify;
macro_rules! parse {
($s:expr) => {{
let l = logger();
Parser::new_for_module(l.clone(), Lexer::new_from_str(l, $s))
.parse_module()
.unwrap_or_else(|err| panic!("failed to parse module: {:?}", err))
}};
}
macro_rules! test {
($l:expr, $r:expr) => {{
assert_eq_ignore_span!(fold(parse!($l), Simplify), parse!($r))
}};
}
macro_rules! test_expr {
($l:expr, $r:expr) => {{
test!(&format!("used(({}))", $l), &format!("used(({}))", $r));
}};
}
/// Should not modify expression.
macro_rules! same_expr {
($l:expr) => {{
test_expr!(&format!("{}", $l), &format!("{}", $l));
}};
}
fn fold<T, F>(t: T, mut f: F) -> T
where
F: Folder<T>,
{
f.fold(t)
}
#[test]
fn add_simple() {
test_expr!("3 + 6", "9");
}
#[test]
fn cond_simple() {
test_expr!("true ? 3 : 6", "3");
test_expr!("false ? 3 : 6", "6");
}
#[test]
fn cond_side_effect() {
test!("new UnknownClass() ? 3 : 6", "new UnknownClass(), 3");
test!("(void fn()) ? 3 : 6", "(void fn()), 6");
}
#[test]
fn oror_non_bool() {
test_expr!("5 || 50", "5")
}
// --------------------------------------------------
// Tests ported from google closure compiler (which is licensed Apache 2.0)
// See https://github.com/google/closure-compiler
// --------------------------------------------------
#[test]
fn undefined_cmp_1() {
test_expr!("undefined == undefined", "true");
test_expr!("undefined == null", "true");
test_expr!("undefined == void 0", "true");
test_expr!("undefined == 0", "false");
test_expr!("undefined == 1", "false");
test_expr!("undefined == 'hi'", "false");
test_expr!("undefined == true", "false");
test_expr!("undefined == false", "false");
test_expr!("undefined === undefined", "true");
test_expr!("undefined === null", "false");
test_expr!("undefined === void 0", "true");
same_expr!("undefined == this");
same_expr!("undefined == x");
test_expr!("undefined != undefined", "false");
test_expr!("undefined != null", "false");
test_expr!("undefined != void 0", "false");
test_expr!("undefined != 0", "true");
test_expr!("undefined != 1", "true");
test_expr!("undefined != 'hi'", "true");
test_expr!("undefined != true", "true");
test_expr!("undefined != false", "true");
test_expr!("undefined !== undefined", "false");
test_expr!("undefined !== void 0", "false");
test_expr!("undefined !== null", "true");
same_expr!("undefined != this");
same_expr!("undefined != x");
test_expr!("undefined < undefined", "false");
test_expr!("undefined > undefined", "false");
test_expr!("undefined >= undefined", "false");
test_expr!("undefined <= undefined", "false");
test_expr!("0 < undefined", "false");
test_expr!("true > undefined", "false");
test_expr!("'hi' >= undefined", "false");
test_expr!("null <= undefined", "false");
test_expr!("undefined < 0", "false");
test_expr!("undefined > true", "false");
test_expr!("undefined >= 'hi'", "false");
test_expr!("undefined <= null", "false");
test_expr!("null == undefined", "true");
test_expr!("0 == undefined", "false");
test_expr!("1 == undefined", "false");
test_expr!("'hi' == undefined", "false");
test_expr!("true == undefined", "false");
test_expr!("false == undefined", "false");
test_expr!("null === undefined", "false");
test_expr!("void 0 === undefined", "true");
test_expr!("undefined == NaN", "false");
test_expr!("NaN == undefined", "false");
test_expr!("undefined == Infinity", "false");
test_expr!("Infinity == undefined", "false");
test_expr!("undefined == -Infinity", "false");
test_expr!("-Infinity == undefined", "false");
test_expr!("({}) == undefined", "false");
test_expr!("undefined == ({})", "false");
test_expr!("([]) == undefined", "false");
test_expr!("undefined == ([])", "false");
test_expr!("(/a/g) == undefined", "false");
test_expr!("undefined == (/a/g)", "false");
test_expr!("(function(){}) == undefined", "false");
test_expr!("undefined == (function(){})", "false");
test_expr!("undefined != NaN", "true");
test_expr!("NaN != undefined", "true");
test_expr!("undefined != Infinity", "true");
test_expr!("Infinity != undefined", "true");
test_expr!("undefined != -Infinity", "true");
test_expr!("-Infinity != undefined", "true");
test_expr!("({}) != undefined", "true");
test_expr!("undefined != ({})", "true");
test_expr!("([]) != undefined", "true");
test_expr!("undefined != ([])", "true");
test_expr!("(/a/g) != undefined", "true");
test_expr!("undefined != (/a/g)", "true");
test_expr!("(function(){}) != undefined", "true");
test_expr!("undefined != (function(){})", "true");
same_expr!("this == undefined");
same_expr!("x == undefined");
}
#[test]
fn undefined_cmp_2() {
test_expr!("\"123\" !== void 0", "true");
test_expr!("\"123\" === void 0", "false");
test_expr!("void 0 !== \"123\"", "true");
test_expr!("void 0 === \"123\"", "false");
}
#[test]
fn undefined_cmp_3() {
test_expr!("\"123\" !== undefined", "true");
test_expr!("\"123\" === undefined", "false");
test_expr!("undefined !== \"123\"", "true");
test_expr!("undefined === \"123\"", "false");
}
#[test]
fn undefined_cmp_4() {
test_expr!("1 !== void 0", "true");
test_expr!("1 === void 0", "false");
test_expr!("null !== void 0", "true");
test_expr!("null === void 0", "false");
test_expr!("undefined !== void 0", "false");
test_expr!("undefined === void 0", "true");
}
#[test]
fn null_cmp_1() {
test_expr!("null == undefined", "true");
test_expr!("null == null", "true");
test_expr!("null == void 0", "true");
test_expr!("null == 0", "false");
test_expr!("null == 1", "false");
test_expr!("null == 'hi'", "false");
test_expr!("null == true", "false");
test_expr!("null == false", "false");
test_expr!("null === undefined", "false");
test_expr!("null === null", "true");
test_expr!("null === void 0", "false");
same_expr!("null === x");
same_expr!("null == this");
same_expr!("null == x");
test_expr!("null != undefined", "false");
test_expr!("null != null", "false");
test_expr!("null != void 0", "false");
test_expr!("null != 0", "true");
test_expr!("null != 1", "true");
test_expr!("null != 'hi'", "true");
test_expr!("null != true", "true");
test_expr!("null != false", "true");
test_expr!("null !== undefined", "true");
test_expr!("null !== void 0", "true");
test_expr!("null !== null", "false");
same_expr!("null != this");
same_expr!("null != x");
test_expr!("null < null", "false");
test_expr!("null > null", "false");
test_expr!("null >= null", "true");
test_expr!("null <= null", "true");
test_expr!("0 < null", "false");
test_expr!("0 > null", "false");
test_expr!("0 >= null", "true");
test_expr!("true > null", "true");
test_expr!("'hi' < null", "false");
test_expr!("'hi' >= null", "false");
test_expr!("null <= null", "true");
test_expr!("null < 0", "false");
test_expr!("null > true", "false");
test_expr!("null < 'hi'", "false");
test_expr!("null >= 'hi'", "false");
test_expr!("null <= null", "true");
test_expr!("null == null", "true");
test_expr!("0 == null", "false");
test_expr!("1 == null", "false");
test_expr!("'hi' == null", "false");
test_expr!("true == null", "false");
test_expr!("false == null", "false");
test_expr!("null === null", "true");
test_expr!("void 0 === null", "false");
test_expr!("null == NaN", "false");
test_expr!("NaN == null", "false");
test_expr!("null == Infinity", "false");
test_expr!("Infinity == null", "false");
test_expr!("null == -Infinity", "false");
test_expr!("-Infinity == null", "false");
test_expr!("({}) == null", "false");
test_expr!("null == ({})", "false");
test_expr!("([]) == null", "false");
test_expr!("null == ([])", "false");
test_expr!("(/a/g) == null", "false");
test_expr!("null == (/a/g)", "false");
test_expr!("(function(){}) == null", "false");
test_expr!("null == (function(){})", "false");
test_expr!("null != NaN", "true");
test_expr!("NaN != null", "true");
test_expr!("null != Infinity", "true");
test_expr!("Infinity != null", "true");
test_expr!("null != -Infinity", "true");
test_expr!("-Infinity != null", "true");
test_expr!("({}) != null", "true");
test_expr!("null != ({})", "true");
test_expr!("([]) != null", "true");
test_expr!("null != ([])", "true");
test_expr!("(/a/g) != null", "true");
test_expr!("null != (/a/g)", "true");
test_expr!("(function(){}) != null", "true");
test_expr!("null != (function(){})", "true");
same_expr!("({a:f()}) == null");
same_expr!("null == ({a:f()})");
same_expr!("([f()]) == null");
same_expr!("null == ([f()])");
same_expr!("this == null");
same_expr!("x == null");
}

View File

@ -0,0 +1,62 @@
extern crate swc_common;
extern crate swc_ecma_ast as ast;
extern crate swc_ecma_parser;
extern crate swc_ecma_transforms as transforms;
#[macro_use]
extern crate testing;
use swc_common::fold::Folder;
use swc_ecma_parser::lexer::Lexer;
use swc_ecma_parser::parser::Parser;
use testing::logger;
use transforms::simplify::Simplify;
macro_rules! parse {
($s:expr) => {{
let l = logger();
Parser::new_for_module(l.clone(), Lexer::new_from_str(l, $s))
.parse_module()
.unwrap_or_else(|err| panic!("failed to parse module: {:?}", err))
}};
}
fn fold<T, F>(t: T, mut f: F) -> T
where
F: Folder<T>,
{
f.fold(t)
}
#[test]
fn add_simple() {
assert_eq_ignore_span!(fold(parse!("use(3 + 6)"), Simplify), parse!("use(9)"))
}
#[test]
fn cond_simple() {
assert_eq_ignore_span!(
fold(parse!("use(true ? 3 : 6)"), Simplify),
parse!("use(3)")
);
assert_eq_ignore_span!(
fold(parse!("use(false ? 3 : 6)"), Simplify),
parse!("use(6)")
);
}
#[test]
fn cond_side_effect() {
assert_eq_ignore_span!(
fold(parse!("new UnknownClass() ? 3 : 6"), Simplify),
parse!("new UnknownClass(), 3")
);
assert_eq_ignore_span!(
fold(parse!("(void fn()) ? 3 : 6"), Simplify),
parse!("(void fn()), 6")
);
}
#[test]
fn oror_non_bool() {
assert_eq_ignore_span!(fold(parse!("use(5 || 50)"), Simplify), parse!("use(5)"))
}