Browse Source

[php] fixed compatibility with PHP 8 (#9964)

Aleksandr Kuzmenko 4 years ago
parent
commit
1b29cb0b0f

+ 1 - 0
extra/CHANGES.txt

@@ -3,6 +3,7 @@
 	General improvements:
 
 	all : added an argument to `haxe.CallStack.exceptionStack` to return full stack up to the topmost call (#9947)
+	php : compatibility with PHP 8
 
 	Bugfixes:
 

+ 57 - 7
src/generators/genphp7.ml

@@ -548,6 +548,42 @@ let rec write_args (str_writer:string->unit) arg_writer (args:'a list) =
 			str_writer ", ";
 			write_args str_writer arg_writer rest
 
+(**
+	PHP 8 doesn't allow mandatory arguments after optional arguments.
+	This function makes optional arguments mandatory from left to right
+	unless there are no more mandatory arguments left to the end of args list.
+
+	E.g `(a:String = null, b:Int, c:Bool = false)` is changed into `(a:String, b:Int, c:Bool = false)`
+*)
+let fix_optional_args is_optional to_mandatory args =
+	let rec find_last_mandatory args i result =
+		match args with
+		| [] ->
+			result
+		| a :: args ->
+			find_last_mandatory args (i + 1) (if is_optional a then result else i)
+	in
+	let last_mandatory = find_last_mandatory args 0 (-1) in
+	List.mapi (fun i a -> if i <= last_mandatory && is_optional a then to_mandatory a else a ) args
+
+let fix_tfunc_args args =
+	fix_optional_args
+		(fun a -> Option.is_some (snd a))
+		(fun (v,_) -> (v,None))
+		args
+
+let fix_tsignature_args args =
+	fix_optional_args
+		(fun (_,optional,_) -> optional)
+		(fun (name,_,t) -> (name,false,t))
+		args
+
+(**
+	Escapes all "$" chars and encloses `str` into double quotes
+*)
+let quote_string str =
+	"\"" ^ (Str.global_replace (Str.regexp "\\$") "\\$" (String.escaped str)) ^ "\""
+
 (**
 	Check if specified field is a var with non-constant expression
 *)
@@ -616,6 +652,18 @@ let is_constant expr =
 		| TConst _ -> true
 		| _ -> false
 
+(**
+	Check if `expr` is a constant zero
+*)
+let is_constant_zero expr =
+	try
+		match expr.eexpr with
+			| TConst (TInt i) when i = Int32.zero -> true
+			| TConst (TFloat s) when float_of_string s = 0.0 -> true
+			| _ -> false
+	with _ ->
+		false
+
 (**
 	Check if `expr` is a concatenation
 *)
@@ -1725,7 +1773,7 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 		method write_closure_declaration func write_arg =
 			vars#dive;
 			self#write "function (";
-			write_args self#write write_arg func.tf_args;
+			write_args self#write write_arg (fix_tfunc_args func.tf_args);
 			self#write ")";
 			(* Generate closure body to separate buffer *)
 			let original_buffer = buffer in
@@ -2090,6 +2138,8 @@ class code_writer (ctx:php_generator_context) hx_type_path php_name =
 					write_method ((self#use boot_type_path) ^ "::shiftRightUnsigned")
 				| OpGt | OpGte | OpLt | OpLte ->
 					compare (" " ^ (Ast.s_binop operation) ^ " ")
+				| OpDiv when is_constant_zero (reveal_expr_with_parenthesis expr2) ->
+					write_method ((self#use boot_type_path) ^ "::divByZero")
 				| _ ->
 					write_binop (" " ^ (Ast.s_binop operation) ^ " ")
 		(**
@@ -3051,7 +3101,7 @@ class virtual type_builder ctx (wrapper:type_wrapper) =
 		method private write_constructor_declaration func =
 			if self#extends_no_constructor then writer#extends_no_constructor;
 			writer#write ("function __construct (");
-			write_args writer#write writer#write_function_arg func.tf_args;
+			write_args writer#write writer#write_function_arg (fix_tfunc_args func.tf_args);
 			writer#write ") {\n";
 			writer#indent_more;
 			self#write_instance_initialization;
@@ -3072,7 +3122,7 @@ class virtual type_builder ctx (wrapper:type_wrapper) =
 				else
 					func.tf_args
 			in
-			write_args writer#write writer#write_function_arg args;
+			write_args writer#write writer#write_function_arg (fix_tfunc_args args);
 			writer#write ") ";
 			if not (self#write_body_if_special_method name) then
 				writer#write_expr (inject_defaults ctx func)
@@ -3189,7 +3239,7 @@ class enum_builder ctx (enm:tenum) =
 			writer#indent 1;
 			self#write_doc (DocMethod (args, TEnum (enm, []), (gen_doc_text_opt field.ef_doc)));
 			writer#write_with_indentation ("static public function " ^ name ^ " (");
-			write_args writer#write (writer#write_arg true) args;
+			write_args writer#write (writer#write_arg true) (fix_tsignature_args args);
 			writer#write ") {\n";
 			writer#indent_more;
 			let index_str = string_of_int field.ef_index in
@@ -3659,7 +3709,7 @@ class class_builder ctx (cls:tclass) =
 				| None ->
 					if is_static then writer#write "static ";
 					writer#write ("function " ^ (field_name field) ^ " (");
-					write_args writer#write (writer#write_arg true) args;
+					write_args writer#write (writer#write_arg true) (fix_tsignature_args args);
 					writer#write ")";
 					writer#write " ;\n"
 				| Some { eexpr = TFunction fn } ->
@@ -3682,11 +3732,11 @@ class class_builder ctx (cls:tclass) =
 			(match field.cf_expr with
 				| None -> (* interface *)
 					writer#write " (";
-					write_args writer#write (writer#write_arg true) args;
+					write_args writer#write (writer#write_arg true) (fix_tsignature_args args);
 					writer#write ");\n";
 				| Some { eexpr = TFunction fn } -> (* normal class *)
 					writer#write " (";
-					write_args writer#write writer#write_function_arg fn.tf_args;
+					write_args writer#write writer#write_function_arg (fix_tfunc_args fn.tf_args);
 					writer#write ")\n";
 					writer#write_line "{";
 					writer#indent_more;

+ 9 - 3
std/php/Boot.hx

@@ -107,7 +107,8 @@ class Boot {
 		Check if specified property has getter
 	**/
 	public static function hasGetter(phpClassName:String, property:String):Bool {
-		ensureLoaded(phpClassName);
+		if(!ensureLoaded(phpClassName))
+			return false;
 
 		var has = false;
 		var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName;
@@ -123,7 +124,8 @@ class Boot {
 		Check if specified property has setter
 	**/
 	public static function hasSetter(phpClassName:String, property:String):Bool {
-		ensureLoaded(phpClassName);
+		if(!ensureLoaded(phpClassName))
+			return false;
 
 		var has = false;
 		var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName;
@@ -146,7 +148,7 @@ class Boot {
 		Retrieve metadata for specified class
 	**/
 	public static function getMeta(phpClassName:String):Null<Dynamic> {
-		ensureLoaded(phpClassName);
+		if(!ensureLoaded(phpClassName)) return null;
 		return Global.isset(meta[phpClassName]) ? meta[phpClassName] : null;
 	}
 
@@ -645,6 +647,10 @@ class Boot {
 			return ((code - 0xF0) << 18) + ((Global.ord(s[1]) - 0x80) << 12) + ((Global.ord(s[2]) - 0x80) << 6) + Global.ord(s[3]) - 0x80;
 		}
 	}
+
+	static public function divByZero(value:Float):Float {
+		return value == 0 ? Const.NAN : (value < 0 ? -Const.INF : Const.INF);
+	}
 }
 
 /**

+ 5 - 0
std/php/Global.hx

@@ -967,6 +967,11 @@ extern class Global {
 	**/
 	static function spl_object_hash(obj:{}):String;
 
+	/**
+		@see http://php.net/manual/en/function.spl-object-id.php
+	**/
+	static function spl_object_id(obj:{}):Int;
+
 	/**
 		@see http://php.net/manual/en/function.spl-autoload-call.php
 	**/

+ 5 - 1
std/php/_std/Type.hx

@@ -59,7 +59,11 @@ enum ValueType {
 	public static function getSuperClass(c:Class<Dynamic>):Class<Dynamic> {
 		if (c == null)
 			return null;
-		var parentClass = Global.get_parent_class((cast c).phpClassName);
+		var parentClass = try {
+			Global.get_parent_class((cast c).phpClassName);
+		} catch(e) {
+			return null;
+		}
 		if (!parentClass)
 			return null;
 		return cast Boot.getClass(parentClass);

+ 3 - 1
std/php/_std/sys/io/Process.hx

@@ -151,7 +151,9 @@ class Process {
 		}
 		while (running) {
 			var arr = Syntax.arrayDecl(process);
-			Syntax.suppress(Global.stream_select(arr, arr, arr, null));
+			try {
+				Syntax.suppress(Global.stream_select(arr, arr, arr, null));
+			} catch(_) {}
 			updateStatus();
 		}
 		return _exitCode;

+ 13 - 2
std/php/_std/sys/net/Socket.hx

@@ -152,19 +152,30 @@ class Socket {
 		throw haxe.io.Error.Custom('Error [$code]: $msg');
 	}
 
+
+	/**
+		Since PHP 8 sockets are represented as instances of class \Socket
+
+		TODO:
+			rewrite without `cast` after resolving https://github.com/HaxeFoundation/haxe/issues/9964
+	*/
+	static inline function getSocketId(s:Resource):Int {
+		return PHP_VERSION_ID < 80000 ? Syntax.int(s) : spl_object_id(cast s);
+	}
+
 	public static function select(read:Array<Socket>, write:Array<Socket>, others:Array<Socket>,
 			?timeout:Float):{read:Array<Socket>, write:Array<Socket>, others:Array<Socket>} {
 		var map:Map<Int, Socket> = new Map();
 		inline function addSockets(sockets:Array<Socket>) {
 			if (sockets != null)
 				for (s in sockets)
-					map[Syntax.int(s.__s)] = s;
+					map[getSocketId(s.__s)] = s;
 		}
 		inline function getRaw(sockets:Array<Socket>):Array<Resource> {
 			return sockets == null ? [] : [for (s in sockets) s.__s];
 		}
 		inline function getOriginal(result:Array<Resource>) {
-			return [for (r in result) map[Syntax.int(r)]];
+			return [for (r in result) map[getSocketId(r)]];
 		}
 
 		addSockets(read);