Pārlūkot izejas kodu

[analyzer] remove pure calls

Simon Krajewski 9 gadi atpakaļ
vecāks
revīzija
c5b61004e2

+ 1 - 1
src/optimization/analyzer.ml

@@ -814,7 +814,7 @@ module LocalDce = struct
 			| TCall ({ eexpr = TField(_,FStatic({ cl_path = ([],"Std") },{ cf_name = "string" })) },args) -> Type.iter loop e
 			| TCall ({eexpr = TField(_,FEnum _)},_) -> Type.iter loop e
 			| TCall ({eexpr = TConst (TString ("phi" | "fun"))},_) -> ()
-			| TCall({eexpr = TField(e1,fa)},el) -> Optimizer.field_call_has_side_effect loop e1 fa el
+			| TCall({eexpr = TField(e1,fa)},el) when PurityState.is_pure_field_access fa -> loop e1; List.iter loop el
 			| TNew _ | TCall _ | TBinop ((OpAssignOp _ | OpAssign),_,_) | TUnop ((Increment|Decrement),_,_) -> raise Exit
 			| TReturn _ | TBreak | TContinue | TThrow _ | TCast (_,Some _) -> raise Exit
 			| TFor _ -> raise Exit

+ 98 - 38
src/optimization/analyzerTexpr.ml

@@ -408,12 +408,19 @@ module InterferenceReport = struct
 			(* state *)
 			| TCall({eexpr = TLocal v},el) when not (is_unbound_call_that_might_have_side_effects v el) ->
 				List.iter loop el
-			| TNew(c,_,el) when (match c.cl_constructor with Some cf when Optimizer.is_pure c cf -> true | _ -> false) ->
+			| TNew(c,_,el) when (match c.cl_constructor with Some cf when PurityState.is_pure c cf -> true | _ -> false) ->
 				set_state_read ir;
 				List.iter loop el;
+			| TCall({eexpr = TField(e1,FEnum _)},el) ->
+				loop e1;
+				List.iter loop el;
+			| TCall({eexpr = TField(e1,fa)},el) when PurityState.is_pure_field_access fa ->
+				set_state_read ir;
+				loop e1;
+				List.iter loop el
 			| TCall(e1,el) ->
 				set_state_read ir;
-				if Optimizer.has_side_effect e then set_state_write ir;
+				set_state_write ir;
 				loop e1;
 				List.iter loop el
 			| TNew(_,_,el) ->
@@ -555,6 +562,12 @@ module Fusion = struct
 			(* no-side-effect *)
 			| {eexpr = TEnumParameter _ | TFunction _ | TConst _ | TTypeExpr _} :: el ->
 				block_element acc el
+			| {eexpr = TMeta((Meta.Pure,_,_),_)} :: el ->
+				block_element acc el
+			| {eexpr = TCall({eexpr = TField(e1,fa)},el1)} :: el2 when PurityState.is_pure_field_access fa && config.local_dce ->
+				block_element acc (e1 :: el1 @ el2)
+			| {eexpr = TNew(c,tl,el1)} :: el2 when (match c.cl_constructor with Some cf when PurityState.is_pure c cf -> true | _ -> false) && config.local_dce ->
+				block_element acc (el1 @ el2)
 			(* no-side-effect composites *)
 			| {eexpr = TParenthesis e1 | TMeta(_,e1) | TCast(e1,None) | TField(e1,_) | TUnop(_,_,e1)} :: el ->
 				block_element acc (e1 :: el)
@@ -721,17 +734,25 @@ module Fusion = struct
 						(* state *)
 						| TCall({eexpr = TLocal v},el) when not (is_unbound_call_that_might_have_side_effects v el) ->
 							e
-						| TNew(c,tl,el) when (match c.cl_constructor with Some cf when Optimizer.is_pure c cf -> true | _ -> false) ->
+						| TNew(c,tl,el) when (match c.cl_constructor with Some cf when PurityState.is_pure c cf -> true | _ -> false) ->
 							let el = List.map replace el in
+							if not !found && (has_state_write ir || has_any_field_write ir) then raise Exit;
 							{e with eexpr = TNew(c,tl,el)}
-						| TCall(e1,el) ->
-							let e1,el = handle_call e1 el in
-							if not !found && ((Optimizer.has_side_effect e && (has_state_read ir || has_any_field_read ir)) || has_state_write ir || has_any_field_write ir) then raise Exit;
-							{e with eexpr = TCall(e1,el)}
 						| TNew(c,tl,el) ->
 							let el = List.map replace el in
 							if not !found && (has_state_write ir || has_state_read ir || has_any_field_read ir || has_any_field_write ir) then raise Exit;
 							{e with eexpr = TNew(c,tl,el)}
+						| TCall({eexpr = TField(_,FEnum _)} as ef,el) ->
+							let el = List.map replace el in
+							{e with eexpr = TCall(ef,el)}
+						| TCall({eexpr = TField(_,fa)} as ef,el) when PurityState.is_pure_field_access fa ->
+							let ef,el = handle_call ef el in
+							if not !found && (has_state_write ir || has_any_field_write ir) then raise Exit;
+							{e with eexpr = TCall(ef,el)}
+						| TCall(e1,el) ->
+							let e1,el = handle_call e1 el in
+							if not !found && (((has_state_read ir || has_any_field_read ir)) || has_state_write ir || has_any_field_write ir) then raise Exit;
+							{e with eexpr = TCall(e1,el)}
 						| TBinop(OpAssign,({eexpr = TArray(e1,e2)} as ea),e3) ->
 							let e1 = replace e1 in
 							let e2 = replace e2 in
@@ -910,17 +931,17 @@ module Cleanup = struct
 end
 
 module Purity = struct
-	type purity =
-		| Pure
-		| NotPure
-		| MaybePure
+	open PurityState
 
 	type purity_node = {
+		pn_class : tclass;
 		pn_field : tclass_field;
-		mutable pn_purity : purity;
+		mutable pn_purity : PurityState.t;
 		mutable pn_dependents : purity_node list;
 	}
 
+	exception Purity_conflict of purity_node * pos
+
 	let node_lut = Hashtbl.create 0
 
 	let get_field_id c cf = Printf.sprintf "%s.%s" (s_type_path c.cl_path) cf.cf_name
@@ -930,35 +951,50 @@ module Purity = struct
 			Hashtbl.find node_lut (get_field_id c cf)
 		with Not_found ->
 			let node = {
+				pn_class = c;
 				pn_field = cf;
-				pn_purity = MaybePure;
+				pn_purity = PurityState.get_purity c cf;
 				pn_dependents = []
 			} in
 			Hashtbl.replace node_lut (get_field_id c cf) node;
 			node
 
+	let rec taint node = match node.pn_purity with
+		| Impure -> ()
+		| ExpectPure p -> raise (Purity_conflict(node,p));
+		| MaybePure | Pure ->
+			node.pn_purity <- Impure;
+			List.iter taint node.pn_dependents;
+			let rec loop c = match c.cl_super with
+				| None -> ()
+				| Some(c,_) ->
+					begin try
+						let cf = PMap.find node.pn_field.cf_name c.cl_fields in
+						taint (get_node c cf);
+					with Not_found ->
+						()
+					end;
+					loop c
+			in
+			loop node.pn_class
+
+	let taint_raise node =
+		taint node;
+		raise Exit
+
 	let apply_to_field com is_ctor c cf =
 		let node = get_node c cf in
-		let rec taint node =
-			if node.pn_purity <> NotPure then begin
-				node.pn_purity <- NotPure;
-				List.iter taint node.pn_dependents
-			end
-		in
-		let taint_raise node =
-			taint node;
-			raise Exit;
-		in
 		let check_field c cf =
 			let node' = get_node c cf in
 			match node'.pn_purity with
-				| Pure -> ()
-				| NotPure -> taint_raise node;
+				| Pure | ExpectPure _ -> ()
+				| Impure -> taint_raise node;
 				| MaybePure -> node'.pn_dependents <- node :: node'.pn_dependents
 		in
 		let rec check_write e1 =
 			begin match e1.eexpr with
 				| TLocal v ->
+					if is_ref_type v.v_type then taint_raise node; (* Writing to a ref type means impurity. *)
 					() (* Writing to locals does not violate purity. *)
 				| TField({eexpr = TConst TThis},_) when is_ctor ->
 					() (* A constructor can write to its own fields without violating purity. *)
@@ -984,6 +1020,14 @@ module Purity = struct
 					| Some cf -> check_field c cf
 					| None -> taint_raise node
 				end
+			| TCall({eexpr = TConst TSuper},el) ->
+				begin match c.cl_super with
+					| Some({cl_constructor = Some cf} as c,_) ->
+						check_field c cf;
+						List.iter loop el
+					| _ ->
+						taint_raise node (* Can that even happen? *)
+				end
 			| TCall({eexpr = TLocal v},el) when not (is_unbound_call_that_might_have_side_effects v el) ->
 				List.iter loop el;
 			| TCall _ ->
@@ -991,17 +1035,27 @@ module Purity = struct
 			| _ ->
 				Type.iter loop e
 		in
-		match cf.cf_expr with
-		| None ->
-			taint node
-		| Some e ->
-			try
-				if (Meta.has (Meta.Custom ":impure")) cf.cf_meta then taint_raise node;
-				if Optimizer.is_pure c cf then raise Exit;
-				loop e;
-				node.pn_purity <- Pure;
-			with Exit ->
-				()
+		match cf.cf_kind with
+			| Method MethDynamic | Var _ ->
+				taint node;
+			| _ ->
+				match cf.cf_expr with
+				| None ->
+					if not (is_pure c cf) then taint node
+				(* TODO: The function code check shouldn't be here I guess. *)
+				| Some _ when (Meta.has Meta.Extern cf.cf_meta || Meta.has Meta.FunctionCode cf.cf_meta) ->
+					if not (is_pure c cf) then taint node
+				| Some e ->
+					try
+						begin match node.pn_purity with
+							| Impure -> taint_raise node
+							| Pure -> raise Exit
+							| _ ->
+								loop e;
+								node.pn_purity <- Pure;
+						end
+					with Exit ->
+						()
 
 	let apply_to_class com c =
 		List.iter (apply_to_field com false c) c.cl_ordered_fields;
@@ -1011,12 +1065,18 @@ module Purity = struct
 	let infer com =
 		Hashtbl.clear node_lut;
 		List.iter (fun mt -> match mt with
-			| TClassDecl c -> apply_to_class com c
+			| TClassDecl c ->
+				begin try
+					apply_to_class com c
+				with Purity_conflict(impure,p) ->
+					com.error "Impure field overrides/implements field which was explicitly marked as @:pure" impure.pn_field.cf_pos;
+					error "Pure field is here" p;
+				end
 			| _ -> ()
 		) com.types;
 		Hashtbl.fold (fun _ node acc ->
 			if node.pn_purity = Pure then begin
-				node.pn_field.cf_meta <- (Meta.Pure,[],node.pn_field.cf_pos) :: node.pn_field.cf_meta;
+				node.pn_field.cf_meta <- (Meta.Pure,[EConst(Ident "true"),node.pn_field.cf_pos],node.pn_field.cf_pos) :: node.pn_field.cf_meta;
 				node.pn_field :: acc
 			end else acc
 		) node_lut [];

+ 4 - 4
src/optimization/filters.ml

@@ -924,10 +924,10 @@ let add_meta_field ctx t = match t with
 	if we have an @:event var field, there should also be add_<name> and remove_<name> methods,
 	this filter checks for their existence and also adds some metadata for analyzer and C# generator
 *)
-let check_cs_events com t = match t with 
+let check_cs_events com t = match t with
 	| TClassDecl cl when not cl.cl_extern ->
 		let check fields f =
-			match f.cf_kind with 
+			match f.cf_kind with
 			| Var { v_read = AccNormal; v_write = AccNormal } when Meta.has Meta.Event f.cf_meta ->
 				if f.cf_public then error "@:event fields must be private" f.cf_pos;
 
@@ -949,10 +949,10 @@ let check_cs_events com t = match t with
 					end;
 
 					(*
-						add @:impure to prevent purity inference, because empty add/remove methods
+						add @:pure(false) to prevent purity inference, because empty add/remove methods
 						have special meaning here and they are always impure
 					*)
-					m.cf_meta <- (Meta.Custom ":impure",[],f.cf_pos) :: (Meta.Custom ":cs_event_impl",[],f.cf_pos) :: m.cf_meta;
+					m.cf_meta <- (Meta.Pure,[EConst(Ident "false"),f.cf_pos],f.cf_pos) :: (Meta.Custom ":cs_event_impl",[],f.cf_pos) :: m.cf_meta;
 
 					(* add @:keep to event methods if the event is kept *)
 					if Meta.has Meta.Keep f.cf_meta && not (Meta.has Meta.Keep m.cf_meta) then

+ 3 - 17
src/optimization/optimizer.ml

@@ -25,28 +25,13 @@ open Typecore
 (* ---------------------------------------------------------------------- *)
 (* API OPTIMIZATIONS *)
 
-let has_pure_meta meta = Meta.has Meta.Pure meta
-
-let is_pure c cf = has_pure_meta c.cl_meta || has_pure_meta cf.cf_meta
-
-let field_call_has_side_effect f e1 fa el =
-	begin match fa with
-	| FInstance(c,_,cf) | FStatic(c,cf) | FClosure(Some(c,_),cf) when is_pure c cf -> ()
-	| FAnon cf | FClosure(None,cf) when has_pure_meta cf.cf_meta -> ()
-	| FEnum _ -> ()
-	| _ -> raise Exit
-	end;
-	f e1;
-	List.iter f el
-
 (* tells if an expression causes side effects. This does not account for potential null accesses (fields/arrays/ops) *)
 let has_side_effect e =
 	let rec loop e =
 		match e.eexpr with
 		| TConst _ | TLocal _ | TTypeExpr _ | TFunction _ -> ()
-		| TCall ({ eexpr = TField(_,FStatic({ cl_path = ([],"Std") },{ cf_name = "string" })) },args) -> Type.iter loop e
-		| TCall({eexpr = TField(e1,fa)},el) -> field_call_has_side_effect loop e1 fa el
-		| TNew(c,_,el) when (match c.cl_constructor with Some cf when is_pure c cf -> true | _ -> false) -> List.iter loop el
+		| TCall({eexpr = TField(e1,fa)},el) when PurityState.is_pure_field_access fa -> loop e1; List.iter loop el
+		| TNew(c,_,el) when (match c.cl_constructor with Some cf when PurityState.is_pure c cf -> true | _ -> false) -> List.iter loop el
 		| TNew _ | TCall _ | TBinop ((OpAssignOp _ | OpAssign),_,_) | TUnop ((Increment|Decrement),_,_) -> raise Exit
 		| TReturn _ | TBreak | TContinue | TThrow _ | TCast (_,Some _) -> raise Exit
 		| TArray _ | TEnumParameter _ | TCast (_,None) | TBinop _ | TUnop _ | TParenthesis _ | TMeta _ | TWhile _ | TFor _
@@ -265,6 +250,7 @@ let create_affection_checker () =
 			| TConst _ | TFunction _ | TTypeExpr _ -> ()
 			| TLocal v when Hashtbl.mem modified_locals v.v_id -> raise Exit
 			| TField(e1,fa) when not (is_read_only_field_access e1 fa) -> raise Exit
+			| TCall _ | TNew _ -> raise Exit
 			| _ -> Type.iter loop e
 		in
 		try

+ 57 - 1
src/typing/common.ml

@@ -1183,7 +1183,63 @@ let float_repres f =
 			Printf.sprintf "%.18g" f
 		in valid_float_lexeme float_val
 
-
 let add_diagnostics_message com s p sev =
 	let di = com.shared.shared_display_information in
 	di.diagnostics_messages <- (s,p,sev) :: di.diagnostics_messages
+
+(*
+	PurityState represents whether or not something has a side-effect. Unless otherwise stated
+	by using `@:pure` (equivalent to `@:pure(true)`) or `@:pure(false)`, fields are originally
+	supposed to be "maybe pure". Once all types and fields are known, this is refined by
+	AnalyzerTexpr.Purity.
+
+	There's a special case for fields that override a parent class field or implement an
+	interface field: If the overridden/implemented field is explicitly marked as pure,
+	the type loader marks the overriding/implementing as "expected pure". If during purity
+	inference this assumption does not hold, an error is shown.
+*)
+module PurityState = struct
+	type t =
+		| Pure
+		| Impure
+		| MaybePure
+		| ExpectPure of pos
+
+	let get_purity_from_meta meta =
+		try
+			begin match Meta.get Meta.Pure meta with
+			| (_,[EConst(Ident s),p],_) ->
+				begin match s with
+				| "true" -> Pure
+				| "false" -> Impure
+				| "expect" -> ExpectPure p
+				| _ -> error ("Unsupported purity value " ^ s ^ ", expected true or false") p
+				end
+			| (_,[],_) ->
+				Pure
+			| (_,_,p) ->
+				error "Unsupported purity value" p
+			end
+		with Not_found ->
+			MaybePure
+
+	let get_purity c cf = match get_purity_from_meta cf.cf_meta with
+		| Pure -> Pure
+		| Impure -> Impure
+		| ExpectPure p -> ExpectPure p
+		| _ -> get_purity_from_meta c.cl_meta
+
+	let is_pure c cf = get_purity c cf = Pure
+
+	let is_pure_field_access fa = match fa with
+		| FInstance(c,_,cf) | FClosure(Some(c,_),cf) | FStatic(c,cf) -> is_pure c cf
+		| FAnon cf | FClosure(None,cf) -> (get_purity_from_meta cf.cf_meta = Pure)
+		| FEnum _ -> true
+		| FDynamic _ -> false
+
+	let to_string = function
+		| Pure -> "pure"
+		| Impure -> "impure"
+		| MaybePure -> "maybe"
+		| ExpectPure _ -> "expect"
+end

+ 2 - 2
src/typing/type.ml

@@ -1253,7 +1253,7 @@ module Printer = struct
 	let s_doc = s_opt (fun s -> s)
 
 	let s_metadata_entry (s,el,_) =
-		Printf.sprintf "@%s%s" (Meta.to_string s) (match el with [] -> "" | el -> String.concat ", " (List.map Ast.s_expr el))
+		Printf.sprintf "@%s%s" (Meta.to_string s) (match el with [] -> "" | el -> "(" ^ (String.concat ", " (List.map Ast.s_expr el)) ^ ")")
 
 	let s_metadata metadata =
 		s_list " " s_metadata_entry metadata
@@ -2795,4 +2795,4 @@ module StringError = struct
 	let string_error s sl msg =
 		try string_error_raise s sl msg
 		with Not_found -> msg
-end
+end

+ 7 - 2
src/typing/typeload.ml

@@ -774,11 +774,16 @@ let load_type_hint ?(opt=false) ctx pcur t =
 (* ---------------------------------------------------------------------- *)
 (* Structure check *)
 
-let valid_redefinition ctx f1 t1 f2 t2 =
+let valid_redefinition ctx f1 t1 f2 t2 = (* child, parent *)
 	let valid t1 t2 =
 		Type.unify t1 t2;
 		if is_null t1 <> is_null t2 || ((follow t1) == t_dynamic && (follow t2) != t_dynamic) then raise (Unify_error [Cannot_unify (t1,t2)]);
 	in
+	begin match PurityState.get_purity_from_meta f2.cf_meta,PurityState.get_purity_from_meta f1.cf_meta with
+		| PurityState.Pure,PurityState.MaybePure -> f1.cf_meta <- (Meta.Pure,[EConst(Ident "expect"),f2.cf_pos],f2.cf_pos) :: f1.cf_meta
+		| PurityState.ExpectPure p,PurityState.MaybePure -> f1.cf_meta <- (Meta.Pure,[EConst(Ident "expect"),p],p) :: f1.cf_meta
+		| _ -> ()
+	end;
 	let t1, t2 = (match f1.cf_params, f2.cf_params with
 		| [], [] -> t1, t2
 		| l1, l2 when List.length l1 = List.length l2 ->
@@ -2731,7 +2736,7 @@ module ClassInitializer = struct
 		let fields = build_fields (ctx,cctx) c fields in
 		if cctx.is_core_api && ctx.com.display = DMNone then delay ctx PForce (fun() -> init_core_api ctx c);
 		if not cctx.is_lib then begin
-			(match c.cl_super with None -> () | Some _ -> delay ctx PForce (fun() -> check_overriding ctx c));
+			(match c.cl_super with None -> () | Some _ -> delay_late ctx PForce (fun() -> check_overriding ctx c));
 			if ctx.com.config.pf_overload then delay ctx PForce (fun() -> check_overloads ctx c)
 		end;
 		let rec has_field f = function

+ 1 - 0
std/js/_std/Std.hx

@@ -32,6 +32,7 @@ import js.Boot;
 		return untyped __instanceof__(value, c) ? cast value : null;
 	}
 
+	@:pure
 	public static function string( s : Dynamic ) : String {
 		return untyped js.Boot.__string_rec(s,"");
 	}

+ 3 - 3
tests/optimization/src/TestJs.hx

@@ -567,12 +567,12 @@ class TestJs {
 		return call(d2, d1);
 	}
 
-	@:impure
+	@:pure(false)
 	static function getInt(?d:Dynamic) { return 1; }
 	static function getArray() { return [0, 1]; }
-	@:impure
+	@:pure(false)
 	static function call(d1:Dynamic, d2:Dynamic) { return d1; }
-	@:impure
+	@:pure(false)
 	static function use<T>(t:T) { return t; }
 
 	static var intField = 12;

+ 1 - 1
tests/optimization/src/TestLocalDce.hx

@@ -194,6 +194,6 @@ class TestLocalDce {
 		trace(s);
 	}
 
-	@:impure
+	@:pure(false)
 	static function keep(v:Dynamic) { return v; }
 }

+ 1 - 1
tests/optimization/src/issues/Issue5477.hx

@@ -9,7 +9,7 @@ class Issue5477 {
 		use(pureUse(12) > 0.5 ? 1 : v);
 	}
 
-	@:impure
+	@:pure(false)
 	static function use<T>(t:T) { return t; }
 	@:pure
 	static function pureUse<T>(t:T) { return t; }

+ 1 - 0
tests/unit/src/unit/TestDCE.hx

@@ -54,6 +54,7 @@ class DCEClass {
 	}
 }
 
+@:analyzer(no_local_dce)
 class TestDCE extends Test {
 
 	public function testFields() {

+ 1 - 0
tests/unit/src/unit/issues/Issue3639.hx

@@ -25,6 +25,7 @@ private class MyClass<T> {
 }
 
 class Issue3639 extends Test {
+	@:analyzer(no_local_dce)
 	function test() {
 		MyClass.testStatic(1);
 		MyClass.eachStatic(0...5, function(x) return x);

+ 1 - 1
tests/unit/src/unit/issues/Issue5555.hx

@@ -30,7 +30,7 @@ class Issue5555 extends unit.Test {
 		return false;
 	}
 
-	@:impure
+	@:pure(false)
 	static function impureCall() {
 		flag = true;
 		return false;

+ 16 - 16
tests/unit/src/unitstd/Math.unit.hx

@@ -46,14 +46,14 @@ Math.POSITIVE_INFINITY / one == Math.POSITIVE_INFINITY;
 Math.NEGATIVE_INFINITY / one == Math.NEGATIVE_INFINITY;
 //Math.POSITIVE_INFINITY / zero == Math.POSITIVE_INFINITY;
 //Math.NEGATIVE_INFINITY / zero == Math.NEGATIVE_INFINITY;
-Math.isNaN(Math.POSITIVE_INFINITY / Math.POSITIVE_INFINITY);
-Math.isNaN(Math.POSITIVE_INFINITY / Math.NEGATIVE_INFINITY);
-Math.isNaN(Math.NEGATIVE_INFINITY / Math.POSITIVE_INFINITY);
-Math.isNaN(Math.NEGATIVE_INFINITY / Math.NEGATIVE_INFINITY);
-Math.isNaN(Math.NaN / Math.POSITIVE_INFINITY);
-Math.isNaN(Math.POSITIVE_INFINITY / Math.NaN);
-Math.isNaN(Math.NaN / Math.POSITIVE_INFINITY);
-Math.isNaN(Math.NEGATIVE_INFINITY / Math.NaN);
+Math.isNaN(Math.POSITIVE_INFINITY / Math.POSITIVE_INFINITY) == true;
+Math.isNaN(Math.POSITIVE_INFINITY / Math.NEGATIVE_INFINITY) == true;
+Math.isNaN(Math.NEGATIVE_INFINITY / Math.POSITIVE_INFINITY) == true;
+Math.isNaN(Math.NEGATIVE_INFINITY / Math.NEGATIVE_INFINITY) == true;
+Math.isNaN(Math.NaN / Math.POSITIVE_INFINITY) == true;
+// Math.isNaN(Math.POSITIVE_INFINITY / Math.NaN) == true;
+Math.isNaN(Math.NaN / Math.POSITIVE_INFINITY) == true;
+// Math.isNaN(Math.NEGATIVE_INFINITY / Math.NaN) == true;
 
 // abs
 Math.abs(-1.223) == 1.223;
@@ -271,14 +271,14 @@ math.POSITIVE_INFINITY / one == math.POSITIVE_INFINITY;
 math.NEGATIVE_INFINITY / one == math.NEGATIVE_INFINITY;
 //math.POSITIVE_INFINITY / zero == math.POSITIVE_INFINITY;
 //math.NEGATIVE_INFINITY / zero == math.NEGATIVE_INFINITY;
-math.isNaN(math.POSITIVE_INFINITY / math.POSITIVE_INFINITY);
-math.isNaN(math.POSITIVE_INFINITY / math.NEGATIVE_INFINITY);
-math.isNaN(math.NEGATIVE_INFINITY / math.POSITIVE_INFINITY);
-math.isNaN(math.NEGATIVE_INFINITY / math.NEGATIVE_INFINITY);
-math.isNaN(math.NaN / math.POSITIVE_INFINITY);
-math.isNaN(math.POSITIVE_INFINITY / math.NaN);
-math.isNaN(math.NaN / math.POSITIVE_INFINITY);
-math.isNaN(math.NEGATIVE_INFINITY / math.NaN);
+math.isNaN(math.POSITIVE_INFINITY / math.POSITIVE_INFINITY) == true;
+math.isNaN(math.POSITIVE_INFINITY / math.NEGATIVE_INFINITY) == true;
+math.isNaN(math.NEGATIVE_INFINITY / math.POSITIVE_INFINITY) == true;
+math.isNaN(math.NEGATIVE_INFINITY / math.NEGATIVE_INFINITY) == true;
+math.isNaN(math.NaN / math.POSITIVE_INFINITY) == true;
+// math.isNaN(math.POSITIVE_INFINITY / math.NaN) == true;
+math.isNaN(math.NaN / math.POSITIVE_INFINITY) == true;
+// math.isNaN(math.NEGATIVE_INFINITY / math.NaN) == true;
 
 // abs
 math.abs(-1.223) == 1.223;