├── .gitignore ├── README.md ├── config.m4 ├── php_scalar.c ├── php_scalar.h └── tests ├── scalar-basic.phpt └── scalar-error.phpt /.gitignore: -------------------------------------------------------------------------------- 1 | /.deps 2 | /.libs/ 3 | /Makefile 4 | /Makefile.fragments 5 | /Makefile.global 6 | /Makefile.objects 7 | /acinclude.m4 8 | /aclocal.m4 9 | /build/ 10 | /config.cache 11 | /config.guess 12 | /config.h 13 | /config.h.in 14 | /config.log 15 | /config.nice 16 | /config.status 17 | /config.sub 18 | /configure 19 | /configure.in 20 | /install-sh 21 | /libtool 22 | /ltmain.sh 23 | /missing 24 | /mkinstalldirs 25 | /modules/ 26 | /php_test_results_*.txt 27 | /run-tests.php 28 | /*.lo 29 | /*.la 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-scalar 2 | ========== 3 | 4 | Scalar type hinting for PHP 5 | 6 | function foo(int $bar) { 7 | echo "bar is $bar\n"; 8 | } 9 | foo(123); // works 10 | foo(4.56); // throws an error 11 | 12 | Types: 13 | * bool 14 | * int 15 | * float 16 | * num (either int of float) 17 | * string 18 | * scalar (any of the above types) 19 | * object (of any class) 20 | * resource 21 | 22 | Note that you cannot specify default values for scalar types because the parser doesn't support it. 23 | This extension was a quick and dirty proof-of-concept. A proper implementation will require altering 24 | the parser which can't be done from an extension (without stupid amounts of work). 25 | 26 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension scalar 3 | 4 | PHP_ARG_ENABLE(scalar, whether to enable scalar type hints, 5 | [ --enable-scalar Enable scalar type hints]) 6 | 7 | if test "$PHP_SCALAR" != "no"; then 8 | PHP_NEW_EXTENSION(scalar, php_scalar.c, $ext_shared) 9 | fi 10 | -------------------------------------------------------------------------------- /php_scalar.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2006 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.0 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_0.txt. | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: Sara Golemon | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | /* $Id$ */ 20 | 21 | #include "php_scalar.h" 22 | 23 | #if ZEND_MODULE_API_NO > 20131217 24 | #define ZEND_ENGINE_2_7 25 | #endif 26 | #if ZEND_MODULE_API_NO > 20131105 27 | #define ZEND_ENGINE_2_6 28 | #endif 29 | #if ZEND_MODULE_API_NO > 20121211 30 | #define ZEND_ENGINE_2_5 31 | #endif 32 | #if ZEND_MODULE_API_NO > 20100524 33 | #define ZEND_ENGINE_2_4 34 | #endif 35 | #if ZEND_MODULE_API_NO > 20090625 36 | #define ZEND_ENGINE_2_3 37 | #endif 38 | #if ZEND_MODULE_API_NO > 20050922 39 | #define ZEND_ENGINE_2_2 40 | #endif 41 | #if ZEND_MODULE_API_NO > 20050921 42 | #define ZEND_ENGINE_2_1 43 | #endif 44 | 45 | #define SCALAR_UNKNOWN 0 46 | #define SCALAR_BOOL 1 47 | #define SCALAR_INT 2 48 | #define SCALAR_FLOAT 3 49 | #define SCALAR_NUMBER 4 50 | #define SCALAR_STRING 5 51 | #define SCALAR_SCALAR 6 52 | #define SCALAR_OBJECT 7 53 | #define SCALAR_RESOURCE 8 54 | 55 | inline zend_uchar scalar_get_type(zend_arg_info *info) { 56 | if (!info) return SCALAR_UNKNOWN; 57 | #define X(ptype, stype) \ 58 | if (((sizeof(#ptype) - 1) == info->class_name_len) && \ 59 | (!strcasecmp(#ptype, info->class_name))) { \ 60 | return stype; \ 61 | } 62 | 63 | X(bool, SCALAR_BOOL) 64 | X(int, SCALAR_INT) 65 | X(float, SCALAR_FLOAT) 66 | X(num, SCALAR_NUMBER) 67 | X(string, SCALAR_STRING) 68 | X(scalar, SCALAR_SCALAR) 69 | X(object, SCALAR_OBJECT) 70 | X(resource, SCALAR_RESOURCE) 71 | #undef X 72 | return SCALAR_UNKNOWN; 73 | } 74 | 75 | #if defined(ZEND_ENGINE_2_6) 76 | #define PHP_SCALAR_OPCODE_COUNT 165 77 | #elif defined(ZEND_ENGINE_2_5) 78 | #define PHP_SCALAR_OPCODE_COUNT 163 79 | #elif defined(ZEND_ENGINE_2_4) 80 | #define PHP_SCALAR_OPCODE_COUNT 158 81 | #elif defined(ZEND_ENGINE_2_3) 82 | #define PHP_SCALAR_OPCODE_COUNT 153 83 | #elif defined(ZEND_ENGINE_2_1) 84 | #define PHP_SCALAR_OPCODE_COUNT 150 85 | #else 86 | # error "Upgrade PHP, damnit" 87 | #endif 88 | 89 | #define PHP_SCALAR_OPHANDLER_COUNT ((25 * (PHP_SCALAR_OPCODE_COUNT + 1)) + 1) 90 | #define PHP_SCALAR_REPLACE_OPCODE(opname) { int i; for(i = 5; i < 25; i++) if (php_scalar_opcode_handlers[(opname*25) + i]) php_scalar_opcode_handlers[(opname*25) + i] = php_scalar_op_##opname; } 91 | 92 | static opcode_handler_t *php_scalar_original_opcode_handlers; 93 | static opcode_handler_t php_scalar_opcode_handlers[PHP_SCALAR_OPHANDLER_COUNT]; 94 | 95 | static zval **scalar_vm_stack_get_arg(int requested_arg TSRMLS_DC) { 96 | zend_execute_data *ex = EG(current_execute_data)->prev_execute_data; 97 | void **p = ex->function_state.arguments; 98 | int arg_count = (int)(zend_uintptr_t) *p; 99 | 100 | if (requested_arg > arg_count) { 101 | return NULL; 102 | } 103 | return (zval**)p - arg_count + requested_arg - 1; 104 | } 105 | 106 | static zend_bool scalar_validate_type(zval *param, zend_uchar exp) { 107 | switch (exp) { 108 | case SCALAR_BOOL: 109 | return (Z_TYPE_P(param) == IS_BOOL); 110 | case SCALAR_INT: 111 | return (Z_TYPE_P(param) == IS_LONG); 112 | case SCALAR_FLOAT: 113 | return (Z_TYPE_P(param) == IS_DOUBLE); 114 | case SCALAR_NUMBER: 115 | return (Z_TYPE_P(param) == IS_LONG) || 116 | (Z_TYPE_P(param) == IS_DOUBLE); 117 | case SCALAR_STRING: 118 | return (Z_TYPE_P(param) == IS_STRING); 119 | case SCALAR_SCALAR: 120 | return (Z_TYPE_P(param) == IS_BOOL) || 121 | (Z_TYPE_P(param) == IS_LONG) || 122 | (Z_TYPE_P(param) == IS_DOUBLE) || 123 | (Z_TYPE_P(param) == IS_STRING); 124 | case SCALAR_OBJECT: 125 | return (Z_TYPE_P(param) == IS_OBJECT); 126 | case SCALAR_RESOURCE: 127 | return (Z_TYPE_P(param) == IS_RESOURCE); 128 | } 129 | return 0; 130 | } 131 | 132 | static int scalar_decode(zend_op *opline) { 133 | int ret = opline->opcode * 25; 134 | switch (opline->op1_type) { 135 | case IS_CONST: break; 136 | case IS_TMP_VAR: ret += 5; break; 137 | case IS_VAR: ret += 10; break; 138 | case IS_UNUSED: ret += 15; break; 139 | case IS_CV: ret += 20; break; 140 | } 141 | switch (opline->op2_type) { 142 | case IS_CONST: break; 143 | case IS_TMP_VAR: ret += 1; break; 144 | case IS_VAR: ret += 2; break; 145 | case IS_UNUSED: ret += 3; break; 146 | case IS_CV: ret += 4; break; 147 | } 148 | return ret; 149 | } 150 | 151 | static int ZEND_FASTCALL php_scalar_op_ZEND_RECV(ZEND_OPCODE_HANDLER_ARGS) { 152 | zend_op *opline = execute_data->opline; 153 | zend_uint arg_num = opline->op1.num; 154 | zend_uchar exp = scalar_get_type( (arg_num <= EG(active_op_array)->num_args) 155 | ? &EG(active_op_array)->arg_info[arg_num-1] : NULL ); 156 | zval **param = scalar_vm_stack_get_arg(arg_num TSRMLS_CC); 157 | 158 | if (!param || !scalar_validate_type(*param, exp) || (exp == SCALAR_UNKNOWN)) { 159 | return php_scalar_original_opcode_handlers[scalar_decode(opline)](ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 160 | } else { 161 | /* Hide the type hint, but otherwise pass-through - JANKY */ 162 | zend_arg_info *info = &EG(active_op_array)->arg_info[arg_num-1]; 163 | const char *class_name = info->class_name; 164 | int class_name_len = info->class_name_len; 165 | int type_hint = info->type_hint, ret; 166 | info->class_name = NULL; 167 | info->class_name_len = 0; 168 | info->type_hint = 0; 169 | ret = php_scalar_original_opcode_handlers[scalar_decode(opline)](ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 170 | info->class_name = class_name; 171 | info->class_name_len = class_name_len; 172 | info->type_hint = type_hint; 173 | return ret; 174 | } 175 | } 176 | 177 | static int ZEND_FASTCALL php_scalar_op_ZEND_RECV_INIT(ZEND_OPCODE_HANDLER_ARGS) { 178 | /* For passthroughs to the original, opline will decode itself correctly */ 179 | return php_scalar_op_ZEND_RECV(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 180 | } 181 | 182 | #ifdef ZEND_ENGINE_2_6 183 | static int ZEND_FASTCALL php_scalar_op_ZEND_RECV_VARIADIC(ZEND_OPCODE_HANDLER_ARGS) { 184 | zend_op *opline = execute_data->opline; 185 | zend_uint arg_num = opline->op1.num; 186 | zend_uint arg_count = scalar_vm_stack_get_args_count(TSRMLS_C); 187 | zend_uchar exp = scalar_get_type( (arg_num <= EG(active_op_array)->num_args) 188 | ? &EG(active_op_array)->arg_info[arg_num-1] : NULL ); 189 | zend_bool valid = 1; 190 | 191 | if (exp != SCALAR_UNKNOWN) { 192 | for(;arg_num < arg_count; ++arg_num) { 193 | zval **param = scalar_vm_stack_get_arg(arg_num TSRMLS_CC); 194 | if (!(valid = param && scalar_validate_type(*param, exp))) { 195 | break; 196 | } 197 | } 198 | } 199 | if (!valid || (exp == SCALAR_UNKNOWN)) { 200 | return php_scalar_original_opcode_handlers[scalar_decode(opline)](ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 201 | } else { 202 | /* Hide the type hint, but otherwise pass-through - JANKY */ 203 | zend_arg_info *info = &EG(active_op_array)->arg_info[opline->op1.num-1]; 204 | const char *class_name = info->class_name; 205 | int class_name_len = info->class_name_len, ret; 206 | info->class_name = NULL; 207 | info->class_name_len = 0; 208 | ret = php_scalar_original_opcode_handlers[scalar_decode(opline)](ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 209 | class_name = info->class_name; 210 | class_name_len = info->class_name_len; 211 | return ret; 212 | } 213 | } 214 | #endif /* ZEND_ENGINE_2_6 */ 215 | 216 | /* {{{ MINFO */ 217 | static PHP_MINIT_FUNCTION(scalar) { 218 | memcpy(php_scalar_opcode_handlers, zend_opcode_handlers, sizeof(php_scalar_opcode_handlers)); 219 | php_scalar_original_opcode_handlers = zend_opcode_handlers; 220 | zend_opcode_handlers = php_scalar_opcode_handlers; 221 | 222 | PHP_SCALAR_REPLACE_OPCODE(ZEND_RECV); 223 | PHP_SCALAR_REPLACE_OPCODE(ZEND_RECV_INIT); 224 | #ifdef ZEND_ENGINE_2_6 225 | PHP_SCALAR_REPLACE_OPCODE(ZEND_RECV_VARIADIC); 226 | #endif 227 | 228 | return SUCCESS; 229 | } 230 | /* }}} */ 231 | 232 | /* {{{ scalar_module_entry 233 | */ 234 | zend_module_entry scalar_module_entry = { 235 | STANDARD_MODULE_HEADER, 236 | "scalar", 237 | NULL, /* functions */ 238 | PHP_MINIT(scalar), 239 | NULL, /* MSHUTDOWN */ 240 | NULL, /* RINIT */ 241 | NULL, /* RSHUTDOWN */ 242 | NULL, /* MINFO */ 243 | NO_VERSION_YET, 244 | STANDARD_MODULE_PROPERTIES 245 | }; 246 | /* }}} */ 247 | 248 | #ifdef COMPILE_DL_SCALAR 249 | ZEND_GET_MODULE(scalar) 250 | #endif 251 | 252 | /* 253 | * Local variables: 254 | * tab-width: 4 255 | * c-basic-offset: 4 256 | * End: 257 | * vim600: noet sw=4 ts=4 fdm=marker 258 | * vim<600: noet sw=4 ts=4 259 | */ 260 | -------------------------------------------------------------------------------- /php_scalar.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2006 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.0 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_0.txt. | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: Sara Golemon | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | /* $Id$ */ 20 | 21 | #ifndef PHP_SCALAR_H 22 | #define PHP_SCALAR_H 23 | 24 | #ifdef HAVE_CONFIG_H 25 | #include "config.h" 26 | #endif 27 | 28 | #include "php.h" 29 | 30 | extern zend_module_entry scalar_module_entry; 31 | #define phpext_scalar_ptr &scalar_module_entry 32 | 33 | #endif /* PHP_SCALAR_H */ 34 | 35 | /* 36 | * Local variables: 37 | * tab-width: 4 38 | * c-basic-offset: 4 39 | * End: 40 | * vim600: noet sw=4 ts=4 fdm=marker 41 | * vim<600: noet sw=4 ts=4 42 | */ 43 | -------------------------------------------------------------------------------- /tests/scalar-basic.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Basic scalar type hints 3 | --FILE-- 4 |