[WIP] simplifier

This commit is contained in:
강동윤 2018-01-14 13:33:09 +09:00
parent 015ccb07f4
commit 5a1c844549
8 changed files with 591 additions and 145 deletions

View File

@ -5,7 +5,10 @@
#[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

@ -7,6 +7,7 @@ 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 {
@ -47,10 +48,10 @@ impl Folder<ExprKind> for Simplify {
fn fold_bin(left: Box<Expr>, op: BinaryOp, right: Box<Expr>) -> ExprKind {
let (left, right) = match op {
BinaryOp::Add => return fold_add(left, right),
BinaryOp::LogicalAnd | BinaryOp::LogicalOr => match left.as_bool() {
op!(bin "+") => return fold_add(left, right),
op!("&&") | op!("||") => match left.as_bool() {
(Pure, Known(val)) => {
if op == BinaryOp::LogicalAnd {
if op == op!("&&") {
if val {
// 1 && $right
return right.node;
@ -70,9 +71,9 @@ fn fold_bin(left: Box<Expr>, op: BinaryOp, right: Box<Expr>) -> ExprKind {
}
_ => (left, right),
},
BinaryOp::InstanceOf => (left, right),
op!("instanceof") => (left, right),
BinaryOp::Sub | BinaryOp::Div | BinaryOp::Mod => {
op!(bin "-") | op!("/") | op!("%") => {
// Arithmetic operations
(left, right)
@ -89,7 +90,7 @@ fn fold_add(left: Box<Expr>, right: Box<Expr>) -> ExprKind {
Binary {
left,
op: BinaryOp::Add,
op: op!(bin "+"),
right,
}
}
@ -98,7 +99,7 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
let span = arg.span;
match op {
UnaryOp::TypeOf => {
op!("typeof") => {
let val = match arg.node {
Function(..) => "function",
Lit(Lit::Str(..)) => "string",
@ -107,7 +108,7 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
Lit(Lit::Null) | Object { .. } | Array { .. } => "object",
Unary {
prefix: true,
op: UnaryOp::Void,
op: op!("void"),
..
}
| Ident(Ident {
@ -122,7 +123,7 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
_ => {
return Unary {
prefix: true,
op: UnaryOp::TypeOf,
op: op!("typeof"),
arg,
}
}
@ -130,7 +131,7 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
return Lit(Lit::Str(val.into()));
}
UnaryOp::Bang => match arg.as_bool() {
op!("!") => match arg.as_bool() {
(p, Known(val)) => {
let new = Lit(Lit::Bool(!val));
return if p.is_pure() {
@ -149,8 +150,8 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
}
}
},
UnaryOp::Plus => {}
UnaryOp::Minus => match arg.node {
op!(unary "+") => {}
op!(unary "-") => match arg.node {
Ident(Ident {
sym: js_word!("Infinity"),
..
@ -173,4 +174,18 @@ fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
}
/// Try to fold arithmetic binary operators
fn perform_arithmetic_op(op: BinaryOp, left: Box<Expr>, right: Box<Expr>) -> ExprKind {}
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

@ -81,6 +81,14 @@ impl Folder<StmtKind> for Simplify {
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 }),
},
@ -88,13 +96,58 @@ impl Folder<StmtKind> for Simplify {
if stmts.len() == 0 {
return StmtKind::Empty;
} else if stmts.len() == 1 {
// TODO: Check lexical variable
// 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,
}
}

View File

@ -1,10 +1,59 @@
pub use self::Purity::{MayBeImpure, Pure};
pub use self::Value::{Known, Unknown};
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, Not};
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> {
///
@ -20,7 +69,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
ExprKind::Unary {
prefix: true,
op: UnaryOp::Bang,
op: op!("!"),
ref arg,
} => {
let (p, v) = arg.as_bool();
@ -29,12 +78,12 @@ pub trait ExprExt: Sized + AsRef<Expr> {
ExprKind::Binary {
ref left,
op: op @ BinaryOp::LogicalAnd,
op: op @ op!("&"),
ref right,
}
| ExprKind::Binary {
ref left,
op: op @ BinaryOp::LogicalOr,
op: op @ op!("|"),
ref right,
} => {
// TODO: Ignore purity if value cannot be reached.
@ -45,7 +94,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
if lp + rp == Pure {
return (Pure, lv.and(rv));
}
if op == BinaryOp::LogicalAnd {
if op == op!("&") {
lv.and(rv)
} else {
lv.or(rv)
@ -60,7 +109,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
ExprKind::Unary {
prefix: true,
op: UnaryOp::Void,
op: op!("void"),
arg: _,
} => Known(false),
@ -105,7 +154,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
},
ExprKind::Unary {
prefix: true,
op: UnaryOp::Minus,
op: op!(unary "-"),
arg:
box Expr {
span: _,
@ -118,7 +167,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
} => -INFINITY,
ExprKind::Unary {
prefix: true,
op: UnaryOp::Bang,
op: op!("!"),
ref arg,
} => match arg.as_bool() {
(Pure, Known(v)) => {
@ -132,14 +181,14 @@ pub trait ExprExt: Sized + AsRef<Expr> {
},
ExprKind::Unary {
prefix: true,
op: UnaryOp::Void,
ref arg,
op: op!("void"),
arg: _,
} => {
if arg.may_have_side_effects() {
return Unknown;
} else {
NAN
}
// if arg.may_have_side_effects() {
return Unknown;
// } else {
// NAN
// }
}
ExprKind::Tpl(..) | ExprKind::Object { .. } | ExprKind::Array { .. } => {
@ -160,7 +209,7 @@ pub trait ExprExt: Sized + AsRef<Expr> {
match expr.node {
ExprKind::Lit(ref l) => match *l {
Lit::Str(ref s) => Some(Cow::Borrowed(s)),
Lit::Num(n) => Some(format!("{}", n).into()),
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")),
@ -177,12 +226,12 @@ pub trait ExprExt: Sized + AsRef<Expr> {
},
ExprKind::Unary {
prefix: true,
op: UnaryOp::Void,
op: op!("void"),
..
} => Some(Cow::Borrowed("undefined")),
ExprKind::Unary {
prefix: true,
op: UnaryOp::Bang,
op: op!("!"),
ref arg,
} => match arg.as_bool() {
(Pure, Known(v)) => Some(Cow::Borrowed(if v { "false" } else { "true" })),
@ -224,6 +273,78 @@ pub trait ExprExt: Sized + AsRef<Expr> {
_ => 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> {
@ -284,53 +405,3 @@ impl Add for Purity {
}
}
}
/// 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 {
Str,
Obj,
Undetermined,
}
pub type Bool = Value<bool>;
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,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

@ -1,62 +0,0 @@
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)"))
}